Bladeren bron

test(core): cover config precedence

Dax Raad 3 dagen geleden
bovenliggende
commit
cd64a17e37
2 gewijzigde bestanden met toevoegingen van 106 en 3 verwijderingen
  1. 91 0
      packages/core/test/config/agent.test.ts
  2. 15 3
      packages/core/test/config/config.test.ts

+ 91 - 0
packages/core/test/config/agent.test.ts

@@ -12,6 +12,7 @@ import { LayerNode } from "@opencode-ai/util/effect/layer-node"
 import { FSUtil } from "@opencode-ai/util/fs-util"
 import { Global } from "@opencode-ai/util/global"
 import { Permission } from "@opencode-ai/core/permission"
+import { AgentPlugin } from "@opencode-ai/core/plugin/agent"
 import { AbsolutePath } from "@opencode-ai/core/schema"
 import { ConfigMigrateV1 } from "@opencode-ai/core/v1/config/migrate"
 import { advance, drain } from "../lib/clock"
@@ -59,6 +60,96 @@ describe("ConfigAgentPlugin.Plugin", () => {
     }),
   )
 
+  it.effect("applies remote permission defaults before explicit global and build rules", () =>
+    Effect.gen(function* () {
+      const agents = yield* Agent.Service
+      const global = yield* Global.Service
+      yield* AgentPlugin.Plugin.effect(host({ agent: agentHost(agents) }))
+
+      const entries = [
+        new Document({
+          type: "document",
+          info: decode(
+            ConfigMigrateV1.migrate({
+              permission: {
+                bash: "ask",
+                edit: "ask",
+                webfetch: "ask",
+                read: {
+                  "*": "allow",
+                  "*.env": "deny",
+                  "*.env.*": "deny",
+                  "*.env.example": "allow",
+                  "*.dev.vars": "deny",
+                  "~/.local/share/opencode/mcp-auth.json": "deny",
+                  "$HOME/.local/share/opencode/mcp-auth.json": "deny",
+                },
+                external_directory: {
+                  "*": "ask",
+                  "~/.local/share/opencode/*": "deny",
+                },
+              },
+            }),
+          ),
+        }),
+        new Document({
+          type: "document",
+          info: decode({
+            permissions: [{ action: "*", resource: "*", effect: "allow" }],
+            agents: {
+              build: {
+                permissions: [
+                  { action: "external_directory", resource: "*", effect: "allow" },
+                  {
+                    action: "external_directory",
+                    resource: "~/.local/share/opencode/*",
+                    effect: "deny",
+                  },
+                  { action: "read", resource: "*.env", effect: "deny" },
+                ],
+              },
+            },
+          }),
+        }),
+      ]
+
+      yield* ConfigAgentPlugin.Plugin.effect(host({ agent: agentHost(agents) })).pipe(
+        Effect.provide(Config.testLayer(entries)),
+      )
+
+      const build = yield* agents.get(Agent.defaultID)
+      if (!build) throw new Error("expected configured build agent")
+      const opencodeData = path.join(global.home, ".local", "share", "opencode", "*")
+      const mcpAuth = path.join(global.home, ".local", "share", "opencode", "mcp-auth.json")
+      expect(build.permissions).toEqual([
+        ...defaultPermissions(global),
+        { action: "question", resource: "*", effect: "allow" },
+        { action: "shell", resource: "*", effect: "ask" },
+        { action: "edit", resource: "*", effect: "ask" },
+        { action: "webfetch", resource: "*", effect: "ask" },
+        { action: "read", resource: "*", effect: "allow" },
+        { action: "read", resource: "*.env", effect: "deny" },
+        { action: "read", resource: "*.env.*", effect: "deny" },
+        { action: "read", resource: "*.env.example", effect: "allow" },
+        { action: "read", resource: "*.dev.vars", effect: "deny" },
+        { action: "read", resource: mcpAuth, effect: "deny" },
+        { action: "read", resource: mcpAuth, effect: "deny" },
+        { action: "external_directory", resource: "*", effect: "ask" },
+        { action: "external_directory", resource: opencodeData, effect: "deny" },
+        { action: "*", resource: "*", effect: "allow" },
+        { action: "external_directory", resource: "*", effect: "allow" },
+        { action: "external_directory", resource: opencodeData, effect: "deny" },
+        { action: "read", resource: "*.env", effect: "deny" },
+      ])
+      expect(Permission.evaluate("shell", "bun test", build.permissions).effect).toBe("allow")
+      expect(Permission.evaluate("edit", "src/index.ts", build.permissions).effect).toBe("allow")
+      expect(Permission.evaluate("webfetch", "https://example.com", build.permissions).effect).toBe("allow")
+      expect(Permission.evaluate("read", ".env", build.permissions).effect).toBe("deny")
+      expect(Permission.evaluate("external_directory", opencodeData, build.permissions).effect).toBe("deny")
+      expect(Permission.evaluate("external_directory", "/outside/*", build.permissions).effect).toBe("allow")
+    }),
+  )
+
   it.effect("applies all global permissions before agent-specific permissions", () =>
     Effect.gen(function* () {
       const agents = yield* Agent.Service

+ 15 - 3
packages/core/test/config/config.test.ts

@@ -307,7 +307,7 @@ describe("Config", () => {
     }),
   )
 
-  it.live("loads authenticated wellknown config below project config", () =>
+  it.live("loads authenticated wellknown config before user configuration", () =>
     Effect.acquireUseRelease(
       Effect.promise(() => tmpdir()),
       (tmp) =>
@@ -370,7 +370,13 @@ describe("Config", () => {
           return yield* Effect.gen(function* () {
             const config = yield* Config.Service
             const bus = yield* Bus.Service
-            expect(Config.latest(yield* config.entries(), "shell")).toBe("project")
+            const initial = yield* config.entries()
+            expect(Config.latest(initial, "shell")).toBe("project")
+            expect(
+              initial.flatMap((entry) =>
+                entry.type === "document" && entry.info.shell ? [entry.info.shell] : [],
+              ),
+            ).toEqual(["secret", "global", "project"])
             const updated = yield* bus
               .subscribe(Event.Updated)
               .pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
@@ -378,7 +384,13 @@ describe("Config", () => {
             key = "next"
             yield* bus.publish(Integration.Event.ConnectionUpdated, { integrationID })
             expect(yield* Fiber.join(updated)).toHaveLength(1)
-            expect(Config.latest(yield* config.entries(), "shell")).toBe("project")
+            const refreshed = yield* config.entries()
+            expect(Config.latest(refreshed, "shell")).toBe("project")
+            expect(
+              refreshed.flatMap((entry) =>
+                entry.type === "document" && entry.info.shell ? [entry.info.shell] : [],
+              ),
+            ).toEqual(["next", "global", "project"])
           }).pipe(
             Effect.provide(testLayer(project, global, project, undefined, undefined, credentialNode, wellknownNode)),
           )