| 1 | package capdiag_test |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "path/filepath" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "reasonix/internal/capdiag" |
| 10 | "reasonix/internal/pluginpkg" |
| 11 | ) |
| 12 | |
| 13 | // TestPluginPackageV1FieldsAreReported pins the Manifest v1 additions to the |
| 14 | // plugins report: prompts/themes counts and the runtime flag, in both the |
| 15 | // JSON payload and the text renderer. |
| 16 | func TestPluginPackageV1FieldsAreReported(t *testing.T) { |
| 17 | root := t.TempDir() |
| 18 | home := t.TempDir() |
| 19 | reasonixHome := filepath.Join(home, ".reasonix") |
| 20 | t.Setenv("HOME", home) |
| 21 | t.Setenv("REASONIX_HOME", reasonixHome) |
| 22 | |
| 23 | pluginRoot := filepath.Join(reasonixHome, "plugins", "demo") |
| 24 | write(t, filepath.Join(pluginRoot, pluginpkg.NativeManifest), `{ |
| 25 | "apiVersion": "reasonix.io/plugin/v1", |
| 26 | "name": "demo", |
| 27 | "contributes": { |
| 28 | "prompts": ["prompts"], |
| 29 | "themes": ["themes/*.reasonix-theme"] |
| 30 | }, |
| 31 | "runtime": {"command": "${REASONIX_PLUGIN_ROOT}/bin/demo", "intercepts": ["input.receive"]} |
| 32 | }`) |
| 33 | write(t, filepath.Join(pluginRoot, "prompts", "plan.md"), "---\ndescription: plan\n---\nPlan $ARGUMENTS\n") |
| 34 | write(t, filepath.Join(pluginRoot, "themes", "neon.reasonix-theme"), "theme bytes") |
| 35 | write(t, filepath.Join(pluginRoot, "bin", "demo"), "#!/bin/sh\n") |
| 36 | if err := pluginpkg.Upsert(reasonixHome, pluginpkg.InstalledPlugin{ |
| 37 | Name: "demo", Root: "plugins/demo", ManifestKind: "reasonix", Enabled: true, |
| 38 | }); err != nil { |
| 39 | t.Fatal(err) |
| 40 | } |
| 41 | |
| 42 | r := capdiag.Collect(capdiag.Options{ |
| 43 | Root: root, HomeDir: home, ReasonixHomeDir: reasonixHome, |
| 44 | }) |
| 45 | if len(r.Plugins.Packages) != 1 { |
| 46 | t.Fatalf("plugin packages = %+v, want demo", r.Plugins.Packages) |
| 47 | } |
| 48 | pkg := r.Plugins.Packages[0] |
| 49 | if pkg.Prompts != 1 || pkg.Themes != 1 || !pkg.Runtime { |
| 50 | t.Fatalf("package v1 fields = prompts:%d themes:%d runtime:%v, want 1/1/true", pkg.Prompts, pkg.Themes, pkg.Runtime) |
| 51 | } |
| 52 | if r.SchemaVersion != 1 { |
| 53 | t.Fatalf("SchemaVersion = %d, want the unchanged schema v1", r.SchemaVersion) |
| 54 | } |
| 55 | text := capdiag.RenderText(r) |
| 56 | for _, want := range []string{"prompts=1", "themes=1", "FULL TRUST"} { |
| 57 | if !strings.Contains(text, want) { |
| 58 | t.Fatalf("text report missing %q:\n%s", want, text) |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | raw, err := json.Marshal(pkg) |
| 63 | if err != nil { |
| 64 | t.Fatal(err) |
| 65 | } |
| 66 | for _, key := range []string{`"prompts":1`, `"themes":1`, `"runtime":true`} { |
| 67 | if !strings.Contains(string(raw), key) { |
| 68 | t.Fatalf("package JSON missing %s: %s", key, raw) |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | // TestPluginPackageLegacyOmitsV1Fields pins the omitempty contract: legacy |
| 74 | // packages produce no new JSON keys, so schema v1 consumers see no change. |
| 75 | func TestPluginPackageLegacyOmitsV1Fields(t *testing.T) { |
| 76 | root := t.TempDir() |
| 77 | home := t.TempDir() |
| 78 | reasonixHome := filepath.Join(home, ".reasonix") |
| 79 | t.Setenv("HOME", home) |
| 80 | t.Setenv("REASONIX_HOME", reasonixHome) |
| 81 | |
| 82 | pluginRoot := filepath.Join(reasonixHome, "plugins", "legacy") |
| 83 | write(t, filepath.Join(pluginRoot, pluginpkg.NativeManifest), `{"name":"legacy","skills":["skills"]}`) |
| 84 | write(t, filepath.Join(pluginRoot, "skills", "s", "SKILL.md"), "---\ndescription: s\n---\nS\n") |
| 85 | if err := pluginpkg.Upsert(reasonixHome, pluginpkg.InstalledPlugin{ |
| 86 | Name: "legacy", Root: "plugins/legacy", ManifestKind: "reasonix", Enabled: true, |
| 87 | }); err != nil { |
| 88 | t.Fatal(err) |
| 89 | } |
| 90 | |
| 91 | r := capdiag.Collect(capdiag.Options{ |
| 92 | Root: root, HomeDir: home, ReasonixHomeDir: reasonixHome, |
| 93 | }) |
| 94 | if len(r.Plugins.Packages) != 1 { |
| 95 | t.Fatalf("plugin packages = %+v, want legacy", r.Plugins.Packages) |
| 96 | } |
| 97 | raw, err := json.Marshal(r.Plugins.Packages[0]) |
| 98 | if err != nil { |
| 99 | t.Fatal(err) |
| 100 | } |
| 101 | for _, key := range []string{`"prompts"`, `"themes"`, `"runtime"`} { |
| 102 | if strings.Contains(string(raw), key) { |
| 103 | t.Fatalf("legacy package JSON should omit %s: %s", key, raw) |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 |