324 lines · 11.0 KB · TypeScript
RawDownload
import { describe, it, expect } from "@effect/vitest";import { Effect } from "effect"; import { ProviderKey, ToolAddress, createExecutor } from "@executor-js/sdk";import { makeInMemoryBlobStore, pluginBlobStore } from "@executor-js/sdk/core";import { makeTestConfig } from "@executor-js/sdk/testing"; import { makeOnePasswordStore, onepasswordPlugin, resolveConfiguredRef } from "./plugin";import type { OnePasswordService } from "./service";import { OnePasswordError } from "./errors";import { OnePasswordConfig, DesktopAppAuth } from "./types"; // removed: v1 routed configure/removeConfig through an explicit `ScopeId`// (`executor.onepassword.configure(config, ScopeId.make("test-scope"))`) and// asserted provider registration via `executor.secrets.providers()`. v2 deletes// the scope stack and the secrets table: config is a single owner-partitioned// blob the extension derives from the executor's owner binding, and credential// providers are discovered through `executor.providers.list()`. const ONEPASSWORD = ProviderKey.make("onepassword"); const twoVaultConfig = OnePasswordConfig.make({  auth: DesktopAppAuth.make({    kind: "desktop-app",    accountName: "my.1password.com",  }),  vaults: [    { id: "vault-123", name: "Personal" },    { id: "vault-456", name: "Work" },  ],  name: "1Password",}); describe("onepassword plugin", () => {  it.effect("registers onepassword as a credential provider", () =>    Effect.gen(function* () {      const executor = yield* createExecutor(        makeTestConfig({ plugins: [onepasswordPlugin()] as const }),      );      const providers = yield* executor.providers.list();      expect(providers).toContain(ONEPASSWORD);    }),  );   it.effect("configure / getConfig / removeConfig round-trip via blob store", () =>    Effect.gen(function* () {      const executor = yield* createExecutor(        makeTestConfig({ plugins: [onepasswordPlugin()] as const }),      );       const initial = yield* executor.onepassword.getConfig();      expect(initial).toBeNull();       yield* executor.onepassword.configure(twoVaultConfig);       const loaded = yield* executor.onepassword.getConfig();      expect(loaded?.vaults).toEqual([        { id: "vault-123", name: "Personal" },        { id: "vault-456", name: "Work" },      ]);      expect(loaded?.name).toBe("1Password");      expect(loaded?.auth.kind).toBe("desktop-app");       yield* executor.onepassword.removeConfig();      const afterRemove = yield* executor.onepassword.getConfig();      expect(afterRemove).toBeNull();    }),  );   it.effect("getConfig redacts the service-account token", () =>    Effect.gen(function* () {      const executor = yield* createExecutor(        makeTestConfig({ plugins: [onepasswordPlugin()] as const }),      );       yield* executor.onepassword.configure(        OnePasswordConfig.make({          auth: { kind: "service-account", token: "super-secret-token" },          vaults: [{ id: "vault-123", name: "CI" }],          name: "CI",        }),      );       const loaded = yield* executor.onepassword.getConfig();      expect(loaded?.auth.kind).toBe("service-account");      // The token must never be surfaced through the redacted projection.      expect(JSON.stringify(loaded)).not.toContain("super-secret-token");    }),  );   it.effect("exposes provider configuration as agent-callable static tools", () =>    Effect.gen(function* () {      const executor = yield* createExecutor(        makeTestConfig({ plugins: [onepasswordPlugin()] as const }),      );       const configured = yield* executor.execute(        ToolAddress.make("executor.onepassword.configure"),        {          auth: { kind: "desktop-app", accountName: "my.1password.com" },          vaults: [            { id: "vault-123", name: "Personal" },            { id: "vault-456", name: "Work" },          ],          name: "1Password",        },        { onElicitation: "accept-all" },      );       expect(configured).toEqual({ ok: true, data: { configured: true } });      expect(        yield* executor.execute(ToolAddress.make("executor.onepassword.getConfig"), {}),      ).toMatchObject({        ok: true,        data: {          config: {            vaults: [              { id: "vault-123", name: "Personal" },              { id: "vault-456", name: "Work" },            ],            name: "1Password",          },        },      });       const removed = yield* executor.execute(        ToolAddress.make("executor.onepassword.removeConfig"),        {},        { onElicitation: "accept-all" },      );       expect(removed).toEqual({ ok: true, data: { removed: true } });      expect(yield* executor.onepassword.getConfig()).toBeNull();    }),  );   it.effect("status reports not-configured before configure", () =>    Effect.gen(function* () {      const executor = yield* createExecutor(        makeTestConfig({ plugins: [onepasswordPlugin()] as const }),      );      const status = yield* executor.onepassword.status();      expect(status.connected).toBe(false);      expect(status.error).toBe("Not configured");    }),  );}); // ---------------------------------------------------------------------------// Stored-config compatibility — blobs written before multi-vault support hold// `{ auth, vaultId, name }`. Reads normalize that to a one-element vaults// array; the next save writes the current shape.// --------------------------------------------------------------------------- describe("onepassword store", () => {  const makeStore = () => {    const blobs = pluginBlobStore(      makeInMemoryBlobStore(),      { org: "org_test", user: null },      "onepassword",    );    return { blobs, store: makeOnePasswordStore(blobs) };  };   it.effect("upgrades a legacy single-vault blob on read", () =>    Effect.gen(function* () {      const { blobs, store } = makeStore();      yield* blobs.put(        "config",        JSON.stringify({          auth: { kind: "desktop-app", accountName: "my.1password.com" },          vaultId: "vault-123",          name: "Personal",        }),        { owner: "org" },      );       const config = yield* store.getConfig();      expect(config).toEqual({        auth: { kind: "desktop-app", accountName: "my.1password.com" },        vaults: [{ id: "vault-123", name: "Personal" }],        name: "Personal",      });    }),  );   it.effect("persists and reads back the multi-vault shape", () =>    Effect.gen(function* () {      const { store } = makeStore();      yield* store.saveConfig(twoVaultConfig, "org");      const config = yield* store.getConfig();      expect(config).toEqual(twoVaultConfig);    }),  );}); // ---------------------------------------------------------------------------// Explicit ref resolution — vault-qualified refs resolve directly; bare refs// must locate exactly one item, and a multi-vault match is an explicit// ambiguity, never a precedence pick.// --------------------------------------------------------------------------- const fakeService = (  itemsByVault: Readonly<Record<string, readonly { id: string; title: string }[]>>,  onResolve?: (uri: string) => void,): OnePasswordService => ({  resolveSecret: (uri) => {    onResolve?.(uri);    return Effect.succeed(`secret:${uri}`);  },  listVaults: () => Effect.succeed(Object.keys(itemsByVault).map((id) => ({ id, title: id }))),  listItems: (vaultId) => {    const items = itemsByVault[vaultId];    return items === undefined      ? Effect.fail(new OnePasswordError({ operation: "item listing", message: "no such vault" }))      : Effect.succeed(items);  },}); describe("resolveConfiguredRef", () => {  it.effect("resolves a fully-qualified op:// URI in a configured vault as-is", () =>    Effect.gen(function* () {      const svc = fakeService({});      const result = yield* resolveConfiguredRef(        svc,        twoVaultConfig,        "op://vault-456/item-abc/password",      );      expect(result).toEqual({        kind: "resolved",        value: "secret:op://vault-456/item-abc/password",      });    }),  );   it.effect("appends the credential field to a picker-shaped op://vault/item ref", () =>    Effect.gen(function* () {      const svc = fakeService({});      const result = yield* resolveConfiguredRef(svc, twoVaultConfig, "op://vault-123/item-abc");      expect(result).toEqual({        kind: "resolved",        value: "secret:op://vault-123/item-abc/credential",      });    }),  );   it.effect("accepts an op:// URI addressed by vault name", () =>    Effect.gen(function* () {      const svc = fakeService({});      const result = yield* resolveConfiguredRef(svc, twoVaultConfig, "op://Work/item/password");      expect(result).toEqual({ kind: "resolved", value: "secret:op://Work/item/password" });    }),  );   it.effect("reports an op:// URI outside the configured vaults", () =>    Effect.gen(function* () {      const svc = fakeService({});      const result = yield* resolveConfiguredRef(        svc,        twoVaultConfig,        "op://vault-999/item/password",      );      expect(result).toEqual({ kind: "outside-vaults" });    }),  );   it.effect("resolves a bare ref that matches exactly one item across vaults", () =>    Effect.gen(function* () {      const svc = fakeService({        "vault-123": [{ id: "item-1", title: "GitHub Token" }],        "vault-456": [{ id: "item-2", title: "Stripe Key" }],      });      const result = yield* resolveConfiguredRef(svc, twoVaultConfig, "GitHub Token");      expect(result).toEqual({        kind: "resolved",        value: "secret:op://vault-123/item-1/credential",      });    }),  );   it.effect("fails a bare ref that matches in two vaults with the vaults named", () =>    Effect.gen(function* () {      const svc = fakeService({        "vault-123": [{ id: "item-1", title: "GitHub Token" }],        "vault-456": [{ id: "item-2", title: "GitHub Token" }],      });      const result = yield* resolveConfiguredRef(svc, twoVaultConfig, "GitHub Token");      expect(result).toEqual({        kind: "ambiguous",        matches: [          {            vaultId: "vault-123",            vaultName: "Personal",            itemId: "item-1",            itemTitle: "GitHub Token",          },          { vaultId: "vault-456", vaultName: "Work", itemId: "item-2", itemTitle: "GitHub Token" },        ],      });    }),  );   it.effect("treats duplicate titles inside one vault as ambiguous too", () =>    Effect.gen(function* () {      const svc = fakeService({        "vault-123": [          { id: "item-1", title: "GitHub Token" },          { id: "item-9", title: "GitHub Token" },        ],        "vault-456": [],      });      const result = yield* resolveConfiguredRef(svc, twoVaultConfig, "GitHub Token");      expect(result.kind).toBe("ambiguous");    }),  );   it.effect("reports not-found for a bare ref matching nothing", () =>    Effect.gen(function* () {      const svc = fakeService({ "vault-123": [], "vault-456": [] });      const result = yield* resolveConfiguredRef(svc, twoVaultConfig, "missing");      expect(result).toEqual({ kind: "not-found" });    }),  );});