test(core): add suite to CI
This commit is contained in:
parent
0cf3ee4406
commit
2311f202cf
9 changed files with 28 additions and 254 deletions
|
|
@ -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\`);`)
|
||||
|
|
|
|||
|
|
@ -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* () {
|
||||
|
|
|
|||
|
|
@ -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* () {
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>[] = []
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@
|
|||
"outputs": [],
|
||||
"passThroughEnv": ["*"]
|
||||
},
|
||||
"@opencode-ai/core#test": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": [],
|
||||
"passThroughEnv": ["*"]
|
||||
},
|
||||
"@opencode-ai/app#test": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue