fix(migration): write config before sidecar updates
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "fs"
|
||||
import { tmpdir } from "os"
|
||||
import { join } from "path"
|
||||
import { migrateConfigFile } from "./config-migration"
|
||||
import { getSidecarPath } from "./migrations-sidecar"
|
||||
|
||||
const createdDirectories: string[] = []
|
||||
const MIGRATION_KEY = "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"
|
||||
|
||||
function createWorkdir(): string {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "omo-config-migration-"))
|
||||
createdDirectories.push(workdir)
|
||||
return workdir
|
||||
}
|
||||
|
||||
function createLegacyConfig(): Record<string, unknown> {
|
||||
return {
|
||||
agents: {
|
||||
prometheus: { model: "anthropic/claude-opus-4-5" },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const directory of createdDirectories.splice(0)) {
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
describe("migrateConfigFile sidecar write ordering", () => {
|
||||
test("writes the migrated config before recording the sidecar when both writes succeed", () => {
|
||||
// given
|
||||
const workdir = createWorkdir()
|
||||
const configPath = join(workdir, "oh-my-opencode.json")
|
||||
const rawConfig = createLegacyConfig()
|
||||
|
||||
writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n")
|
||||
|
||||
// when
|
||||
const needsWrite = migrateConfigFile(configPath, rawConfig)
|
||||
|
||||
// then
|
||||
expect(needsWrite).toBe(true)
|
||||
expect(rawConfig._migrations).toBeUndefined()
|
||||
expect((rawConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
|
||||
"anthropic/claude-opus-4-6",
|
||||
)
|
||||
|
||||
const persistedConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>
|
||||
expect(persistedConfig._migrations).toBeUndefined()
|
||||
expect((persistedConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
|
||||
"anthropic/claude-opus-4-6",
|
||||
)
|
||||
|
||||
const sidecar = JSON.parse(readFileSync(getSidecarPath(configPath), "utf-8")) as {
|
||||
appliedMigrations: string[]
|
||||
}
|
||||
expect(sidecar.appliedMigrations).toEqual([MIGRATION_KEY])
|
||||
})
|
||||
|
||||
test("skips the sidecar when the config write fails so the migration retries on next startup", () => {
|
||||
// given
|
||||
const workdir = createWorkdir()
|
||||
const configPath = join(workdir, "missing-parent", "oh-my-opencode.json")
|
||||
const firstAttemptConfig = createLegacyConfig()
|
||||
|
||||
// when
|
||||
const firstAttemptNeedsWrite = migrateConfigFile(configPath, firstAttemptConfig)
|
||||
|
||||
// then
|
||||
expect(firstAttemptNeedsWrite).toBe(true)
|
||||
expect(existsSync(getSidecarPath(configPath))).toBe(false)
|
||||
expect(firstAttemptConfig._migrations).toEqual([MIGRATION_KEY])
|
||||
|
||||
// given
|
||||
mkdirSync(join(workdir, "missing-parent"), { recursive: true })
|
||||
writeFileSync(configPath, JSON.stringify(createLegacyConfig(), null, 2) + "\n")
|
||||
const retriedConfig = createLegacyConfig()
|
||||
|
||||
// when
|
||||
const retriedNeedsWrite = migrateConfigFile(configPath, retriedConfig)
|
||||
|
||||
// then
|
||||
expect(retriedNeedsWrite).toBe(true)
|
||||
expect(retriedConfig._migrations).toBeUndefined()
|
||||
expect((retriedConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
|
||||
"anthropic/claude-opus-4-6",
|
||||
)
|
||||
expect(existsSync(getSidecarPath(configPath))).toBe(true)
|
||||
})
|
||||
|
||||
test("preserves _migrations in the config when the sidecar write fails after the config write succeeds", () => {
|
||||
// given
|
||||
const workdir = createWorkdir()
|
||||
const configPath = join(workdir, "oh-my-opencode.json")
|
||||
const rawConfig = createLegacyConfig()
|
||||
|
||||
writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n")
|
||||
mkdirSync(getSidecarPath(configPath))
|
||||
|
||||
// when
|
||||
const needsWrite = migrateConfigFile(configPath, rawConfig)
|
||||
|
||||
// then
|
||||
expect(needsWrite).toBe(true)
|
||||
expect(rawConfig._migrations).toEqual([MIGRATION_KEY])
|
||||
expect((rawConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
|
||||
"anthropic/claude-opus-4-6",
|
||||
)
|
||||
|
||||
const persistedConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>
|
||||
expect(persistedConfig._migrations).toEqual([MIGRATION_KEY])
|
||||
expect((persistedConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
|
||||
"anthropic/claude-opus-4-6",
|
||||
)
|
||||
expect(statSync(getSidecarPath(configPath)).isDirectory()).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user