| 1 | package conformance |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "runtime" |
| 8 | "testing" |
| 9 | |
| 10 | "reasonix/internal/extension" |
| 11 | "reasonix/internal/extension/protocol" |
| 12 | "reasonix/internal/extension/sidecar" |
| 13 | "reasonix/internal/pluginpkg" |
| 14 | ) |
| 15 | |
| 16 | func copyFixtureFile(t *testing.T, src, dst string, mode os.FileMode) { |
| 17 | t.Helper() |
| 18 | data, err := os.ReadFile(src) |
| 19 | if err != nil { |
| 20 | t.Fatalf("read fixture %s: %v", src, err) |
| 21 | } |
| 22 | if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { |
| 23 | t.Fatalf("create fixture directory: %v", err) |
| 24 | } |
| 25 | if err := os.WriteFile(dst, data, mode); err != nil { |
| 26 | t.Fatalf("write fixture %s: %v", dst, err) |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | // installExamplePackage assembles the built SDK example into the same layout |
| 31 | // as an installed plugin, persists enabled state, then reloads it exclusively |
| 32 | // through pluginpkg's v2 parser and installed-package inventory. |
| 33 | func installExamplePackage(t *testing.T) (string, pluginpkg.InstalledPackage) { |
| 34 | t.Helper() |
| 35 | home := t.TempDir() |
| 36 | root := pluginpkg.InstallRoot(home, testPluginID) |
| 37 | copyFixtureFile(t, filepath.Join(exampleRoot, pluginpkg.NativeManifest), filepath.Join(root, pluginpkg.NativeManifest), 0o644) |
| 38 | binaryName := "full-sidecar" |
| 39 | if runtime.GOOS == "windows" { |
| 40 | binaryName += ".exe" |
| 41 | } |
| 42 | copyFixtureFile(t, examplePath, filepath.Join(root, "bin", binaryName), 0o755) |
| 43 | |
| 44 | pkg, warnings, err := pluginpkg.ParseDir(root) |
| 45 | if err != nil { |
| 46 | t.Fatalf("ParseDir(installed fullsidecar): %v", err) |
| 47 | } |
| 48 | if len(warnings) != 0 { |
| 49 | t.Fatalf("ParseDir warnings = %v", warnings) |
| 50 | } |
| 51 | installed := pluginpkg.InstalledPlugin{ |
| 52 | Name: testPluginID, |
| 53 | Root: pluginpkg.RelativeRoot(home, root), |
| 54 | Version: pkg.Manifest.Version, |
| 55 | ManifestKind: pkg.ManifestKind, |
| 56 | Enabled: true, |
| 57 | } |
| 58 | if err := pluginpkg.Upsert(home, installed); err != nil { |
| 59 | t.Fatalf("Upsert installed fullsidecar: %v", err) |
| 60 | } |
| 61 | loaded, warnings := pluginpkg.LoadInstalled(home) |
| 62 | if len(warnings) != 0 { |
| 63 | t.Fatalf("LoadInstalled warnings = %v", warnings) |
| 64 | } |
| 65 | if len(loaded) != 1 { |
| 66 | t.Fatalf("LoadInstalled = %d packages, want 1", len(loaded)) |
| 67 | } |
| 68 | return home, loaded[0] |
| 69 | } |
| 70 | |
| 71 | func TestInstallableFixtureManifestCoversEveryRuntimeSurface(t *testing.T) { |
| 72 | _, item := installExamplePackage(t) |
| 73 | pkg := item.Package |
| 74 | if pkg.Manifest.APIVersion != pluginpkg.ManifestAPIVersionV2 { |
| 75 | t.Fatalf("apiVersion = %q, want %q", pkg.Manifest.APIVersion, pluginpkg.ManifestAPIVersionV2) |
| 76 | } |
| 77 | if pkg.Manifest.Name != testPluginID || pkg.Manifest.Version != "1.0.0" { |
| 78 | t.Fatalf("manifest identity = %q/%q", pkg.Manifest.Name, pkg.Manifest.Version) |
| 79 | } |
| 80 | rt := pkg.Manifest.Runtime |
| 81 | if rt == nil || rt.Command != "${REASONIX_PLUGIN_ROOT}/bin/full-sidecar" { |
| 82 | t.Fatalf("runtime = %+v", rt) |
| 83 | } |
| 84 | wantIntercepts := map[string]bool{"input.receive": true, "tool.before": true, "system_prompt.build": true, "session.start": true} |
| 85 | if len(rt.Intercepts) != len(wantIntercepts) { |
| 86 | t.Fatalf("runtime.intercepts = %v", rt.Intercepts) |
| 87 | } |
| 88 | for _, point := range rt.Intercepts { |
| 89 | if !wantIntercepts[point] { |
| 90 | t.Fatalf("unexpected runtime intercept %q", point) |
| 91 | } |
| 92 | } |
| 93 | wantCapabilities := map[string]bool{"interceptors": true, "strategies": true, "providers": true, "ui": true} |
| 94 | if len(rt.Capabilities) != len(wantCapabilities) { |
| 95 | t.Fatalf("runtime.capabilities = %v", rt.Capabilities) |
| 96 | } |
| 97 | for _, capability := range rt.Capabilities { |
| 98 | if !wantCapabilities[capability] { |
| 99 | t.Fatalf("unexpected runtime capability %q", capability) |
| 100 | } |
| 101 | } |
| 102 | if len(rt.Replaces) != 1 || rt.Replaces[0] != "system_prompt" { |
| 103 | t.Fatalf("runtime.replaces = %v", rt.Replaces) |
| 104 | } |
| 105 | wantProvides := map[string]string{ |
| 106 | "plugin/full-sidecar/interceptors/default": "", |
| 107 | "plugin/full-sidecar/strategies/system_prompt": "", |
| 108 | "plugin/full-sidecar/provider/fake/echo": fixtureProviderSchemaHash, |
| 109 | "plugin/full-sidecar/uiaction/demo": fixtureUIActionSchemaHash, |
| 110 | } |
| 111 | if len(pkg.Manifest.Provides) != len(wantProvides) { |
| 112 | t.Fatalf("manifest provides = %+v", pkg.Manifest.Provides) |
| 113 | } |
| 114 | for _, provided := range pkg.Manifest.Provides { |
| 115 | key := provided.Namespace + "/" + provided.Kind + "/" + provided.ID |
| 116 | wantHash, ok := wantProvides[key] |
| 117 | if !ok || provided.SchemaHash != wantHash { |
| 118 | t.Fatalf("provided capability %q has schemaHash %q", key, provided.SchemaHash) |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | func TestInstallableFixtureEnableDisableRemoveAndKernelContributions(t *testing.T) { |
| 124 | home, _ := installExamplePackage(t) |
| 125 | if err := pluginpkg.SetEnabled(home, testPluginID, false); err != nil { |
| 126 | t.Fatalf("disable fixture: %v", err) |
| 127 | } |
| 128 | if packages, warnings := sidecar.LoadRuntimePackages(home); len(packages) != 0 || len(warnings) != 0 { |
| 129 | t.Fatalf("disabled fixture loaded packages=%d warnings=%v", len(packages), warnings) |
| 130 | } |
| 131 | if err := pluginpkg.SetEnabled(home, testPluginID, true); err != nil { |
| 132 | t.Fatalf("enable fixture: %v", err) |
| 133 | } |
| 134 | mgr, warnings, err := sidecar.StartPackages(context.Background(), home, protocol.SessionContext{ |
| 135 | SessionID: "sess-installed", WorkspaceRoot: "/ws", Generation: 1, |
| 136 | }, nil) |
| 137 | if err != nil { |
| 138 | t.Fatalf("StartPackages: %v", err) |
| 139 | } |
| 140 | t.Cleanup(func() { _ = mgr.Close() }) |
| 141 | if len(warnings) != 0 { |
| 142 | t.Fatalf("StartPackages warnings = %v", warnings) |
| 143 | } |
| 144 | want := map[string]bool{ |
| 145 | string(extension.KindInterceptor) + ":input.receive": true, |
| 146 | string(extension.KindInterceptor) + ":tool.before": true, |
| 147 | string(extension.KindInterceptor) + ":system_prompt.build": true, |
| 148 | string(extension.KindInterceptor) + ":session.start": true, |
| 149 | string(extension.KindStrategy) + ":system_prompt": true, |
| 150 | string(extension.KindProvider) + ":fake/echo": true, |
| 151 | string(extension.KindUIAction) + ":demo": true, |
| 152 | } |
| 153 | contributions := mgr.Contributions() |
| 154 | if len(contributions) != len(want) { |
| 155 | t.Fatalf("contributions = %+v", contributions) |
| 156 | } |
| 157 | for _, contribution := range contributions { |
| 158 | key := string(contribution.Kind) + ":" + contribution.ID |
| 159 | if !want[key] { |
| 160 | t.Fatalf("unexpected contribution %q", key) |
| 161 | } |
| 162 | } |
| 163 | if _, ok, err := pluginpkg.Remove(home, testPluginID); err != nil || !ok { |
| 164 | t.Fatalf("remove fixture: ok=%v err=%v", ok, err) |
| 165 | } |
| 166 | if packages, warnings := sidecar.LoadRuntimePackages(home); len(packages) != 0 || len(warnings) != 0 { |
| 167 | t.Fatalf("removed fixture loaded packages=%d warnings=%v", len(packages), warnings) |
| 168 | } |
| 169 | } |
| 170 |