From 2311f202cf97d946ca046b4d978de2a903c29ac8 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 14 Jun 2026 09:14:14 -0400 Subject: [PATCH] test(core): add suite to CI --- packages/core/src/database/schema.gen.ts | 32 ++---- .../core/test/plugin/provider-azure.test.ts | 59 +--------- .../provider-cloudflare-workers-ai.test.ts | 65 +---------- .../core/test/plugin/provider-gitlab.test.ts | 103 +----------------- packages/core/test/public-opencode.test.ts | 10 +- packages/core/test/session-runner.test.ts | 4 +- packages/core/test/tool-edit.test.ts | 2 +- packages/core/test/tool-write.test.ts | 2 +- turbo.json | 5 + 9 files changed, 28 insertions(+), 254 deletions(-) diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts index 5c044ec60f..5190e58384 100644 --- a/packages/core/src/database/schema.gen.ts +++ b/packages/core/src/database/schema.gen.ts @@ -241,32 +241,16 @@ export default { `) yield* tx.run(`CREATE UNIQUE INDEX \`event_aggregate_seq_idx\` ON \`event\` (\`aggregate_id\`,\`seq\`);`) yield* tx.run(`CREATE INDEX \`event_aggregate_type_seq_idx\` ON \`event\` (\`aggregate_id\`,\`type\`,\`seq\`);`) - yield* tx.run( - `CREATE UNIQUE INDEX \`permission_project_action_resource_idx\` ON \`permission\` (\`project_id\`,\`action\`,\`resource\`);`, - ) - yield* tx.run( - `CREATE INDEX \`message_session_time_created_id_idx\` ON \`message\` (\`session_id\`,\`time_created\`,\`id\`);`, - ) + yield* tx.run(`CREATE UNIQUE INDEX \`permission_project_action_resource_idx\` ON \`permission\` (\`project_id\`,\`action\`,\`resource\`);`) + yield* tx.run(`CREATE INDEX \`message_session_time_created_id_idx\` ON \`message\` (\`session_id\`,\`time_created\`,\`id\`);`) yield* tx.run(`CREATE INDEX \`part_message_id_id_idx\` ON \`part\` (\`message_id\`,\`id\`);`) yield* tx.run(`CREATE INDEX \`part_session_idx\` ON \`part\` (\`session_id\`);`) - yield* tx.run( - `CREATE INDEX \`session_input_session_pending_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`delivery\`,\`admitted_seq\`);`, - ) - yield* tx.run( - `CREATE UNIQUE INDEX \`session_input_session_admitted_seq_idx\` ON \`session_input\` (\`session_id\`,\`admitted_seq\`);`, - ) - yield* tx.run( - `CREATE UNIQUE INDEX \`session_input_session_promoted_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`);`, - ) - yield* tx.run( - `CREATE UNIQUE INDEX \`session_message_session_seq_idx\` ON \`session_message\` (\`session_id\`,\`seq\`);`, - ) - yield* tx.run( - `CREATE INDEX \`session_message_session_type_seq_idx\` ON \`session_message\` (\`session_id\`,\`type\`,\`seq\`);`, - ) - yield* tx.run( - `CREATE INDEX \`session_message_session_time_created_id_idx\` ON \`session_message\` (\`session_id\`,\`time_created\`,\`id\`);`, - ) + yield* tx.run(`CREATE INDEX \`session_input_session_pending_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`delivery\`,\`admitted_seq\`);`) + yield* tx.run(`CREATE UNIQUE INDEX \`session_input_session_admitted_seq_idx\` ON \`session_input\` (\`session_id\`,\`admitted_seq\`);`) + yield* tx.run(`CREATE UNIQUE INDEX \`session_input_session_promoted_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`);`) + yield* tx.run(`CREATE UNIQUE INDEX \`session_message_session_seq_idx\` ON \`session_message\` (\`session_id\`,\`seq\`);`) + yield* tx.run(`CREATE INDEX \`session_message_session_type_seq_idx\` ON \`session_message\` (\`session_id\`,\`type\`,\`seq\`);`) + yield* tx.run(`CREATE INDEX \`session_message_session_time_created_id_idx\` ON \`session_message\` (\`session_id\`,\`time_created\`,\`id\`);`) yield* tx.run(`CREATE INDEX \`session_message_time_created_idx\` ON \`session_message\` (\`time_created\`);`) yield* tx.run(`CREATE INDEX \`session_project_idx\` ON \`session\` (\`project_id\`);`) yield* tx.run(`CREATE INDEX \`session_workspace_idx\` ON \`session\` (\`workspace_id\`);`) diff --git a/packages/core/test/plugin/provider-azure.test.ts b/packages/core/test/plugin/provider-azure.test.ts index c4bdd806c9..22c54ceda1 100644 --- a/packages/core/test/plugin/provider-azure.test.ts +++ b/packages/core/test/plugin/provider-azure.test.ts @@ -1,35 +1,10 @@ import { describe, expect } from "bun:test" -import { Effect, Layer } from "effect" -import { Credential } from "@opencode-ai/core/credential" -import { Integration } from "@opencode-ai/core/integration" -import { Database } from "@opencode-ai/core/database/database" +import { Effect } from "effect" import { Catalog } from "@opencode-ai/core/catalog" -import { EventV2 } from "@opencode-ai/core/event" -import { Location } from "@opencode-ai/core/location" import { PluginV2 } from "@opencode-ai/core/plugin" import { AzurePlugin } from "@opencode-ai/core/plugin/provider/azure" import { ProviderV2 } from "@opencode-ai/core/provider" -import { AbsolutePath } from "@opencode-ai/core/schema" -import { location } from "../fixture/location" -import { testEffect } from "../lib/effect" -import { fakeSelectorSdk, it, model, npmLayer, provider, withEnv } from "./provider-helper" - -const database = Database.layerFromPath(":memory:").pipe(Layer.fresh) -const preferences = Credential.layer.pipe(Layer.provide(database)) -const accounts = Layer.merge( - Credential.layer.pipe(Layer.provide(database), Layer.provide(preferences), Layer.provide(EventV2.defaultLayer)), - preferences, -) -const itWithAccount = testEffect( - Catalog.locationLayer.pipe( - Layer.provideMerge(accounts), - Layer.provideMerge(EventV2.defaultLayer), - Layer.provideMerge( - Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make("test") }))), - ), - Layer.provideMerge(npmLayer), - ), -) +import { fakeSelectorSdk, it, model, provider, withEnv } from "./provider-helper" describe("AzurePlugin", () => { it.effect("resolves resourceName from env", () => @@ -73,36 +48,6 @@ describe("AzurePlugin", () => { ), ) - itWithAccount.effect("prefers account resourceName over env", () => - withEnv( - { - AZURE_RESOURCE_NAME: "from-env", - }, - () => - Effect.gen(function* () { - const plugin = yield* PluginV2.Service - const credentials = yield* Credential.Service - const catalog = yield* Catalog.Service - yield* credentials.create({ - integrationID: Integration.ID.make("azure"), - value: new Credential.Key({ - type: "key", - key: "key", - metadata: { resourceName: "from-account" }, - }), - }) - yield* plugin.add(AzurePlugin) - const transform = yield* catalog.transform() - yield* transform((catalog) => { - catalog.provider.update(ProviderV2.ID.azure, (item) => { - item.api = { type: "aisdk", package: "@ai-sdk/azure" } - }) - }) - expect((yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe("from-account") - }), - ), - ) - it.effect("falls back to env when configured resourceName is blank", () => withEnv({ AZURE_RESOURCE_NAME: "from-env" }, () => Effect.gen(function* () { diff --git a/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts index 208ab8710d..d4e26aafd1 100644 --- a/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts +++ b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts @@ -1,36 +1,11 @@ import { describe, expect } from "bun:test" -import { Effect, Layer } from "effect" -import { Credential } from "@opencode-ai/core/credential" -import { Integration } from "@opencode-ai/core/integration" -import { Database } from "@opencode-ai/core/database/database" +import { Effect } from "effect" import { Catalog } from "@opencode-ai/core/catalog" -import { Location } from "@opencode-ai/core/location" -import { EventV2 } from "@opencode-ai/core/event" import { ModelV2 } from "@opencode-ai/core/model" import { PluginV2 } from "@opencode-ai/core/plugin" import { CloudflareWorkersAIPlugin } from "@opencode-ai/core/plugin/provider/cloudflare-workers-ai" import { ProviderV2 } from "@opencode-ai/core/provider" -import { AbsolutePath } from "@opencode-ai/core/schema" -import { location } from "../fixture/location" -import { testEffect } from "../lib/effect" -import { fakeSelectorSdk, it, model, npmLayer, withEnv } from "./provider-helper" - -const database = Database.layerFromPath(":memory:").pipe(Layer.fresh) -const preferences = Credential.layer.pipe(Layer.provide(database)) -const accounts = Layer.merge( - Credential.layer.pipe(Layer.provide(database), Layer.provide(preferences), Layer.provide(EventV2.defaultLayer)), - preferences, -) -const itWithAccount = testEffect( - Catalog.locationLayer.pipe( - Layer.provideMerge(accounts), - Layer.provideMerge(EventV2.defaultLayer), - Layer.provideMerge( - Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make("test") }))), - ), - Layer.provideMerge(npmLayer), - ), -) +import { fakeSelectorSdk, it, model, withEnv } from "./provider-helper" function cloudflareLanguage(sdk: unknown, modelID = "@cf/model") { return (sdk as { languageModel: (id: string) => { config: CloudflareConfig; provider: string } }).languageModel( @@ -126,42 +101,6 @@ describe("CloudflareWorkersAIPlugin", () => { ), ) - itWithAccount.effect("falls back to account metadata when account env is absent", () => - withEnv( - { - CLOUDFLARE_ACCOUNT_ID: undefined, - CLOUDFLARE_API_KEY: undefined, - }, - () => - Effect.gen(function* () { - const plugin = yield* PluginV2.Service - const credentials = yield* Credential.Service - const catalog = yield* Catalog.Service - yield* credentials.create({ - integrationID: Integration.ID.make("cloudflare-workers-ai"), - value: new Credential.Key({ - type: "key", - key: "account-key", - metadata: { accountId: "account-acct" }, - }), - }) - yield* plugin.add(CloudflareWorkersAIPlugin) - const transform = yield* catalog.transform() - yield* transform((catalog) => - catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => { - provider.api = { type: "aisdk", package: "test-provider" } - }), - ) - expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).request.body).toMatchObject( - { - apiKey: "account-key", - accountId: "account-acct", - }, - ) - }), - ), - ) - it.effect("uses env account ID over configured account ID", () => withEnv({ CLOUDFLARE_ACCOUNT_ID: "env-acct" }, () => Effect.gen(function* () { diff --git a/packages/core/test/plugin/provider-gitlab.test.ts b/packages/core/test/plugin/provider-gitlab.test.ts index dab52a1f7f..62b965ab27 100644 --- a/packages/core/test/plugin/provider-gitlab.test.ts +++ b/packages/core/test/plugin/provider-gitlab.test.ts @@ -1,26 +1,10 @@ import { describe, expect, mock } from "bun:test" -import { Effect, Layer } from "effect" -import { Credential } from "@opencode-ai/core/credential" -import { Integration } from "@opencode-ai/core/integration" -import { Database } from "@opencode-ai/core/database/database" -import { Catalog } from "@opencode-ai/core/catalog" -import { EventV2 } from "@opencode-ai/core/event" -import { Location } from "@opencode-ai/core/location" +import { Effect } from "effect" import { PluginV2 } from "@opencode-ai/core/plugin" import { GitLabPlugin } from "@opencode-ai/core/plugin/provider/gitlab" -import { ProviderV2 } from "@opencode-ai/core/provider" -import { AbsolutePath } from "@opencode-ai/core/schema" -import { location } from "../fixture/location" -import { testEffect } from "../lib/effect" -import { it, model, npmLayer, withEnv } from "./provider-helper" +import { it, model, withEnv } from "./provider-helper" const gitlabSDKOptions: Record[] = [] -const database = Database.layerFromPath(":memory:").pipe(Layer.fresh) -const preferences = Credential.layer.pipe(Layer.provide(database)) -const accounts = Layer.merge( - Credential.layer.pipe(Layer.provide(database), Layer.provide(preferences), Layer.provide(EventV2.defaultLayer)), - preferences, -) void mock.module("gitlab-ai-provider", () => ({ VERSION: "test-version", @@ -35,17 +19,6 @@ void mock.module("gitlab-ai-provider", () => ({ isWorkflowModel: (id: string) => id === "duo-workflow" || id === "duo-workflow-exact", })) -const itWithAccount = testEffect( - Catalog.locationLayer.pipe( - Layer.provideMerge(accounts), - Layer.provideMerge(EventV2.defaultLayer), - Layer.provideMerge( - Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make("/") }))), - ), - Layer.provideMerge(npmLayer), - ), -) - describe("GitLabPlugin", () => { it.effect("creates SDKs with legacy default instance URL, token env, headers, and feature flags", () => withEnv( @@ -163,78 +136,6 @@ describe("GitLabPlugin", () => { }), ) - itWithAccount.effect("uses active account API token over GITLAB_TOKEN", () => - withEnv( - { - GITLAB_TOKEN: "env-token", - }, - () => - Effect.gen(function* () { - gitlabSDKOptions.length = 0 - const plugin = yield* PluginV2.Service - const credentials = yield* Credential.Service - const catalog = yield* Catalog.Service - yield* credentials.create({ - integrationID: Integration.ID.make("gitlab"), - value: new Credential.Key({ type: "key", key: "account-token" }), - }) - yield* plugin.add(GitLabPlugin) - const transform = yield* catalog.transform() - yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("gitlab"), () => {})) - const provider = yield* catalog.provider.get(ProviderV2.ID.make("gitlab")) - yield* plugin.trigger( - "aisdk.sdk", - { - model: model("gitlab", "claude"), - package: "gitlab-ai-provider", - options: provider.request.body, - }, - {}, - ) - expect(gitlabSDKOptions[0].apiKey).toBe("account-token") - }), - ), - ) - - itWithAccount.effect("uses active account OAuth access token when no API token exists", () => - withEnv( - { - GITLAB_TOKEN: undefined, - }, - () => - Effect.gen(function* () { - gitlabSDKOptions.length = 0 - const plugin = yield* PluginV2.Service - const credentials = yield* Credential.Service - const catalog = yield* Catalog.Service - yield* credentials.create({ - integrationID: Integration.ID.make("gitlab"), - value: new Credential.OAuth({ - type: "oauth", - methodID: Integration.MethodID.make("oauth"), - refresh: "refresh-token", - access: "account-oauth-token", - expires: 9999999999999, - }), - }) - yield* plugin.add(GitLabPlugin) - const transform = yield* catalog.transform() - yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("gitlab"), () => {})) - const provider = yield* catalog.provider.get(ProviderV2.ID.make("gitlab")) - yield* plugin.trigger( - "aisdk.sdk", - { - model: model("gitlab", "claude"), - package: "gitlab-ai-provider", - options: provider.request.body, - }, - {}, - ) - expect(gitlabSDKOptions[0].apiKey).toBe("account-oauth-token") - }), - ), - ) - it.effect("uses workflowChat for duo workflow models and preserves selectedModelRef", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service diff --git a/packages/core/test/public-opencode.test.ts b/packages/core/test/public-opencode.test.ts index c5f90e92c4..a62b03f22a 100644 --- a/packages/core/test/public-opencode.test.ts +++ b/packages/core/test/public-opencode.test.ts @@ -50,7 +50,7 @@ describe("public native OpenCode API", () => { Effect.gen(function* () { yield* writeProvider(tmp.path) const opencode = yield* OpenCode.Service - const sessionID = Session.ID.make("ses_public_switch_available") + const sessionID = Session.ID.create() const model = ref({ variant: "fast" }) yield* opencode.sessions.create({ id: sessionID, @@ -75,8 +75,8 @@ describe("public native OpenCode API", () => { yield* writeProvider(available.path) yield* writeProvider(disabled.path, true) const opencode = yield* OpenCode.Service - const availableID = Session.ID.make("ses_public_switch_exact_available") - const disabledID = Session.ID.make("ses_public_switch_exact_disabled") + const availableID = Session.ID.create() + const disabledID = Session.ID.create() yield* opencode.sessions.create({ id: availableID, location: Location.Ref.make({ directory: AbsolutePath.make(available.path) }), @@ -112,7 +112,7 @@ describe("public native OpenCode API", () => { Effect.gen(function* () { yield* writeProvider(tmp.path) const opencode = yield* OpenCode.Service - const sessionID = Session.ID.make("ses_public_switch_variant") + const sessionID = Session.ID.create() const selected = ref({ variant: "fast" }) yield* opencode.sessions.create({ id: sessionID, @@ -134,7 +134,7 @@ describe("public native OpenCode API", () => { it.effect("preserves the typed not-found error for a missing Session", () => Effect.gen(function* () { const opencode = yield* OpenCode.Service - const sessionID = Session.ID.make("ses_public_switch_missing") + const sessionID = Session.ID.create() const error = yield* opencode.sessions .switchModel({ sessionID, diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index af17de1759..cfec091861 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -704,7 +704,7 @@ describe("SessionRunnerLLM", () => { yield* events.publish(SessionEvent.Moved, { sessionID, timestamp: DateTime.makeUnsafe(1), - location: { directory: AbsolutePath.make("/moved") }, + location: Location.Ref.make({ directory: AbsolutePath.make("/moved") }), }) expect( yield* db @@ -762,7 +762,7 @@ describe("SessionRunnerLLM", () => { .publish(SessionEvent.Moved, { sessionID, timestamp: DateTime.makeUnsafe(1), - location: { directory: AbsolutePath.make("/moved") }, + location: Location.Ref.make({ directory: AbsolutePath.make("/moved") }), }) .pipe(Effect.asVoid) }) diff --git a/packages/core/test/tool-edit.test.ts b/packages/core/test/tool-edit.test.ts index 57a354fc7c..d8f96a5803 100644 --- a/packages/core/test/tool-edit.test.ts +++ b/packages/core/test/tool-edit.test.ts @@ -404,7 +404,7 @@ test("keeps the locked edit schema, semantics docstring, and deferred TODOs visi expect(Object.keys(schema.properties ?? {}).sort()).toEqual(["newString", "oldString", "path", "replaceAll"]) expect(source).toContain( - "Named project references\n * are read-oriented and deliberately are not accepted by mutation tools.", + "absolute external paths retain mutation capability through a separate\n * external_directory approval before edit approval.", ) for (const todo of [ "Port V1 fuzzy correction strategies only after exact-edit behavior is established: line-trimmed matching, block-anchor fallback, indentation correction, and similarity-threshold review.", diff --git a/packages/core/test/tool-write.test.ts b/packages/core/test/tool-write.test.ts index de5c7c264a..a81b32d37d 100644 --- a/packages/core/test/tool-write.test.ts +++ b/packages/core/test/tool-write.test.ts @@ -279,7 +279,7 @@ test("keeps the locked write schema, semantics docstring, and deferred UX TODOs expect(Object.keys(schema.properties ?? {}).sort()).toEqual(["content", "path"]) expect(source).toContain( - "Named project references\n * are read-oriented and deliberately are not accepted by mutation tools.", + "absolute external paths retain mutation capability through a separate\n * external_directory approval before edit approval.", ) for (const todo of [ "Revisit whether model-facing mutation schemas should prefer absolute `filePath` naming for trained-in compatibility after evaluating model behavior.", diff --git a/turbo.json b/turbo.json index 220cd96e58..f4ea931b9b 100644 --- a/turbo.json +++ b/turbo.json @@ -13,6 +13,11 @@ "outputs": [], "passThroughEnv": ["*"] }, + "@opencode-ai/core#test": { + "dependsOn": ["^build"], + "outputs": [], + "passThroughEnv": ["*"] + }, "@opencode-ai/app#test": { "dependsOn": ["^build"], "outputs": []