diff --git a/docs/superpowers/plans/2026-07-26-oauth-reliability-integrity.md b/docs/superpowers/plans/2026-07-26-oauth-reliability-integrity.md index 798219e8ff..2d573a5037 100644 --- a/docs/superpowers/plans/2026-07-26-oauth-reliability-integrity.md +++ b/docs/superpowers/plans/2026-07-26-oauth-reliability-integrity.md @@ -502,7 +502,7 @@ EOF **Files:** - Modify: `src/cli/doctor.ts` -- Test: `tests/doctor-oauth.test.ts` (or extend `tests/doctor.test.ts`) +- Test: `tests/service/doctor-oauth.test.ts` (or extend `tests/doctor.test.ts`) **Interfaces:** - Consumes: `collectOAuthHealthEntries`, auth store writability checks, refresh lock path helpers if exported @@ -519,7 +519,7 @@ Seed a temp account with `needsReauth`, run the new `collectOAuthDoctorChecks()` - [ ] **Step 2: Run test to verify it fails** -Run: `bun test tests/doctor-oauth.test.ts` +Run: `bun test tests/service/doctor-oauth.test.ts` Expected: FAIL @@ -529,14 +529,14 @@ Add `collectOAuthDoctorChecks(): Array<{ level: "OK" | "WARN"; message: string } - [ ] **Step 4: Run tests** -Run: `bun test tests/doctor-oauth.test.ts tests/doctor.test.ts` +Run: `bun test tests/service/doctor-oauth.test.ts tests/doctor.test.ts` Expected: PASS - [ ] **Step 5: Commit** ```bash -git add src/cli/doctor.ts tests/doctor-oauth.test.ts +git add src/cli/doctor.ts tests/service/doctor-oauth.test.ts git commit -m "$(cat <<'EOF' feat(cli): add OAuth reliability checks to ocx doctor @@ -713,7 +713,7 @@ EOF - [ ] **Step 1: Run verification commands** ```bash -bun test tests/privacy-mask-account.test.ts tests/oauth-log.test.ts tests/oauth-refresh-generic-lock.test.ts tests/oauth-health.test.ts tests/cli-status-oauth-health.test.ts tests/doctor-oauth.test.ts tests/oauth-accounts-api.test.ts tests/codex-metadata-integrity.test.ts +bun test tests/privacy-mask-account.test.ts tests/oauth-log.test.ts tests/oauth-refresh-generic-lock.test.ts tests/oauth-health.test.ts tests/cli-status-oauth-health.test.ts tests/service/doctor-oauth.test.ts tests/oauth-accounts-api.test.ts tests/codex-metadata-integrity.test.ts bun test tests/oauth-refresh.test.ts tests/xai-refresh-lock.test.ts tests/codex-routing.test.ts tests/session-affinity.test.ts tests/codex-auth-context.test.ts bun run test bun run typecheck diff --git a/scripts/test-layout/layout.json b/scripts/test-layout/layout.json index ec8b7f0b73..c7bf18de0a 100644 --- a/scripts/test-layout/layout.json +++ b/scripts/test-layout/layout.json @@ -1226,5 +1226,9 @@ "zz-ci-storage-policy-isolation.test.ts": "ci-workflows", "zz-pr-coderabbit-readiness-revalidation.test.ts": "ci-workflows" }, - "migrated": [] + "migrated": [ + "service", + "update", + "windows" + ] } diff --git a/scripts/test-layout/verify.ts b/scripts/test-layout/verify.ts index 56b9185565..461e763a29 100644 --- a/scripts/test-layout/verify.ts +++ b/scripts/test-layout/verify.ts @@ -35,12 +35,12 @@ export interface VerifyReport { /** * Everything that may name a test path as text. Shared with move.ts so the preflight write set - * and the post-move STALE check see the same files. `devlog/_fin` is history: it records where - * a file lived when the unit closed and is deliberately not rewritten. Open `devlog/_plan` units - * are live documents and are. + * and the post-move STALE check see the same files. `devlog/` is deliberately absent: a unit + * records where a file lived when it was written, and rewriting 59 old plan documents per slice + * (measured on the first slice) buries the real diff without changing what any tool reads. */ export const SWEEP_ROOTS = [ - "tests", "scripts", ".github", "src", "gui/src", "gui/tests", "bin", "docs", "docs-site", "structure", "devlog/_plan", "skills", + "tests", "scripts", ".github", "src", "gui/src", "gui/tests", "bin", "docs", "docs-site", "structure", "skills", "AGENTS.md", "AGENTS_INSTALL.md", "MAINTAINERS.md", "CONTRIBUTING.md", "README.md", "CREDITS.md", "bunfig.toml", "package.json", ".gitignore", ".npmignore", ]; diff --git a/scripts/test.ts b/scripts/test.ts index 6d49e25f4c..0e6721deca 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -330,7 +330,7 @@ export const SERIAL_FULL_SUITE_FILES = [ "issue-452-empty-503.test.ts", "openai-provider-option-e2e.test.ts", "release-helper.test.ts", - "update-stop-first.test.ts", + "update/update-stop-first.test.ts", ] as const; type SerialLaneBasename = (typeof SERIAL_FULL_SUITE_FILES)[number] extends infer P diff --git a/tests/codex-catalog-writer.test.ts b/tests/codex-catalog-writer.test.ts index f5a375546d..ed7bf313bc 100644 --- a/tests/codex-catalog-writer.test.ts +++ b/tests/codex-catalog-writer.test.ts @@ -261,7 +261,7 @@ for (const mutator of mutators) { // both run write → harden → publish). Supplying `io` bypasses the real // implementations, so this proves hardening is REQUESTED on the temp before // publication — not that it restricts. The Windows NTFS ACL that does the actual - // restricting is exercised in tests/windows-secret-acl.test.ts, and the POSIX mode + // restricting is exercised in tests/windows/windows-secret-acl.test.ts, and the POSIX mode // below is the only half `statSync` can observe (Windows reports 0o666 whatever // `chmodSync` did). const tempEffect = effects.find(effect => effect.startsWith("temp:")); diff --git a/tests/grok-lifecycle.test.ts b/tests/grok-lifecycle.test.ts index a9687b9270..a30e716765 100644 --- a/tests/grok-lifecycle.test.ts +++ b/tests/grok-lifecycle.test.ts @@ -20,7 +20,7 @@ function sliceFn(source: string, start: string, end: string): string { // `src/cli/index.ts` runs its command switch on import, so the handlers cannot be called from a // test. Wiring assertions therefore read the source — the house pattern established by -// tests/stale-state-purge.test.ts and tests/uninstall.test.ts. +// tests/service/stale-state-purge.test.ts and tests/uninstall.test.ts. describe("Grok fence lifecycle wiring", () => { test("handleStart syncs the Grok fence outside the Desktop-3P try", () => { const startFn = sliceFn(CLI_SOURCE, "async function handleStart(", "async function handleEnsure("); @@ -380,7 +380,7 @@ describe("POST /api/stop teardown", () => { test("strips the Grok fence on an accepted stop", () => { // The teardown moved to src/server/stop-teardown.ts so a test can call it: the route // schedules process.exit 200ms after answering, which made the inline version - // unreachable. tests/stop-deferred-teardown.test.ts proves the behaviour; this proves + // unreachable. tests/service/stop-deferred-teardown.test.ts proves the behaviour; this proves // the route still delegates to it rather than growing a second copy. const handler = sliceFn(MANAGEMENT_SOURCE, '"/api/stop"', "/api/codex-auth/"); expect(handler).toContain("performStopTeardown(url, { ownsReceipt: deferralMatchesReceipt })"); diff --git a/tests/native-main-claim.test.ts b/tests/native-main-claim.test.ts index 38e325175a..05316650fd 100644 --- a/tests/native-main-claim.test.ts +++ b/tests/native-main-claim.test.ts @@ -215,7 +215,7 @@ describe("the default hardener is actually reached from a claim", () => { * * So this one runs a claim with the DEFAULT hardener and inspects the file it * left behind. On POSIX that is the mode; the Windows branch is proven - * separately in tests/windows-secret-acl.test.ts, where the ACL runner can be + * separately in tests/windows/windows-secret-acl.test.ts, where the ACL runner can be * observed. */ test("a shared claim narrows a permissive claim database to 0600", async () => { diff --git a/tests/autostart-health.test.ts b/tests/service/autostart-health.test.ts similarity index 97% rename from tests/autostart-health.test.ts rename to tests/service/autostart-health.test.ts index e50342334a..639f1b34c3 100644 --- a/tests/autostart-health.test.ts +++ b/tests/service/autostart-health.test.ts @@ -1,10 +1,10 @@ import { describe, expect, test } from "bun:test"; -import { deriveStartupHealth, formatStartupRoutingDetail, startupHealthSummary } from "../src/codex/autostart-health"; -import { unusedProxyWarningLines } from "../src/cli/status"; -import { classifyCodexRouting, hasInjectedCodexRouting } from "../src/codex/inject"; -import { handleManagementAPI } from "../src/server/management-api"; -import { getCachedStartupHealth, invalidateStartupHealthCache, markStartupHealthDiagnosticStale } from "../src/server/startup-health-cache"; -import type { OcxConfig } from "../src/types"; +import { deriveStartupHealth, formatStartupRoutingDetail, startupHealthSummary } from "../../src/codex/autostart-health"; +import { unusedProxyWarningLines } from "../../src/cli/status"; +import { classifyCodexRouting, hasInjectedCodexRouting } from "../../src/codex/inject"; +import { handleManagementAPI } from "../../src/server/management-api"; +import { getCachedStartupHealth, invalidateStartupHealthCache, markStartupHealthDiagnosticStale } from "../../src/server/startup-health-cache"; +import type { OcxConfig } from "../../src/types"; const base = { routingKind: "opencodex-local" as const, @@ -278,7 +278,7 @@ describe("Codex startup health", () => { invalidateStartupHealthCache(); }); }); -import { ManagementRequest as Request } from "./helpers/management-auth"; +import { ManagementRequest as Request } from "../helpers/management-auth"; describe("routing visibility (#2411)", () => { test("formatStartupRoutingDetail renders the token doctor already prints", () => { diff --git a/tests/crash-guard.test.ts b/tests/service/crash-guard.test.ts similarity index 98% rename from tests/crash-guard.test.ts rename to tests/service/crash-guard.test.ts index e879893e11..b09f40029a 100644 --- a/tests/crash-guard.test.ts +++ b/tests/service/crash-guard.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; -import { appendCrashTraceForTests, crashRingEntriesForTests, formatCrashEntry, installCrashGuards, isBenignAbortTeardown, resetCrashRingForTests } from "../src/lib/crash-guard"; -import { RETAINED_TRUNCATION_MARKER, retainedUtf8Bytes } from "../src/lib/admission"; -import { sidecarEnter } from "../src/lib/sidecar-tracker"; +import { appendCrashTraceForTests, crashRingEntriesForTests, formatCrashEntry, installCrashGuards, isBenignAbortTeardown, resetCrashRingForTests } from "../../src/lib/crash-guard"; +import { RETAINED_TRUNCATION_MARKER, retainedUtf8Bytes } from "../../src/lib/admission"; +import { sidecarEnter } from "../../src/lib/sidecar-tracker"; describe("crash-guard diagnostics", () => { test("the 13th fetch trace evicts the oldest and 8 KiB values truncate on UTF-8 boundaries", () => { diff --git a/tests/doctor-codex-envkey-readiness.test.ts b/tests/service/doctor-codex-envkey-readiness.test.ts similarity index 95% rename from tests/doctor-codex-envkey-readiness.test.ts rename to tests/service/doctor-codex-envkey-readiness.test.ts index 646beda0fa..657a97448f 100644 --- a/tests/doctor-codex-envkey-readiness.test.ts +++ b/tests/service/doctor-codex-envkey-readiness.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { collectCodexEnvKeyReadiness } from "../src/cli/doctor"; -import type { CodexShimDiagnostic } from "../src/codex/shim"; +import { collectCodexEnvKeyReadiness } from "../../src/cli/doctor"; +import type { CodexShimDiagnostic } from "../../src/codex/shim"; const config = ` model_provider = "opencodex" diff --git a/tests/doctor-oauth.test.ts b/tests/service/doctor-oauth.test.ts similarity index 95% rename from tests/doctor-oauth.test.ts rename to tests/service/doctor-oauth.test.ts index fb4310312e..f14e15553d 100644 --- a/tests/doctor-oauth.test.ts +++ b/tests/service/doctor-oauth.test.ts @@ -2,11 +2,11 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { mkdirSync, readdirSync, writeFileSync, readFileSync, statSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { collectOAuthDoctorChecks } from "../src/cli/doctor"; -import { MAIN_CODEX_ACCOUNT_ID } from "../src/codex/main-account"; -import { CODEX_REAUTH_ACTION } from "../src/oauth/health"; -import { getAccountSet, getAuthStorePath, markAccountNeedsReauth, saveCredential } from "../src/oauth/store"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +import { collectOAuthDoctorChecks } from "../../src/cli/doctor"; +import { MAIN_CODEX_ACCOUNT_ID } from "../../src/codex/main-account"; +import { CODEX_REAUTH_ACTION } from "../../src/oauth/health"; +import { getAccountSet, getAuthStorePath, markAccountNeedsReauth, saveCredential } from "../../src/oauth/store"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; const origHome = process.env.HOME; const origOcxHome = process.env.OPENCODEX_HOME; diff --git a/tests/doctor-provider-apikey.test.ts b/tests/service/doctor-provider-apikey.test.ts similarity index 96% rename from tests/doctor-provider-apikey.test.ts rename to tests/service/doctor-provider-apikey.test.ts index 7df23a1b37..b8408fcdfd 100644 --- a/tests/doctor-provider-apikey.test.ts +++ b/tests/service/doctor-provider-apikey.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { collectProviderApiKeyDiagnostics } from "../src/cli/doctor"; +import { collectProviderApiKeyDiagnostics } from "../../src/cli/doctor"; const prototypePropertyNames = [ "toString", diff --git a/tests/doctor-service-memory-contract.test.ts b/tests/service/doctor-service-memory-contract.test.ts similarity index 88% rename from tests/doctor-service-memory-contract.test.ts rename to tests/service/doctor-service-memory-contract.test.ts index 21b854764e..7bfbd58285 100644 --- a/tests/doctor-service-memory-contract.test.ts +++ b/tests/service/doctor-service-memory-contract.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { fetchServiceMemory } from "../src/cli/doctor"; +import { fetchServiceMemory } from "../../src/cli/doctor"; type ServiceMemoryDeps = NonNullable[1]>; type TimeoutOptionIsHidden = "timeoutMs" extends keyof ServiceMemoryDeps ? false : true; diff --git a/tests/init-backup-cleanup.test.ts b/tests/service/init-backup-cleanup.test.ts similarity index 96% rename from tests/init-backup-cleanup.test.ts rename to tests/service/init-backup-cleanup.test.ts index 77d6757772..120acb9de5 100644 --- a/tests/init-backup-cleanup.test.ts +++ b/tests/service/init-backup-cleanup.test.ts @@ -2,9 +2,9 @@ import { afterEach, describe, expect, test } from "bun:test"; import { existsSync, mkdtempSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { cleanupOpenAiTierBackupAfterInit } from "../src/cli/init"; -import { classifyOpenAiTierBackup, OpenAiTierRollbackPreserveError, preserveOpenAiTierRollbackSnapshot } from "../src/config"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +import { cleanupOpenAiTierBackupAfterInit } from "../../src/cli/init"; +import { classifyOpenAiTierBackup, OpenAiTierRollbackPreserveError, preserveOpenAiTierRollbackSnapshot } from "../../src/config"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; describe("cleanupOpenAiTierBackupAfterInit", () => { const dirs: string[] = []; diff --git a/tests/init-eof.test.ts b/tests/service/init-eof.test.ts similarity index 92% rename from tests/init-eof.test.ts rename to tests/service/init-eof.test.ts index fae0885c7f..2f2de54d5c 100644 --- a/tests/init-eof.test.ts +++ b/tests/service/init-eof.test.ts @@ -2,7 +2,8 @@ import { afterEach, describe, expect, test } from "bun:test"; import { existsSync, mkdtempSync} from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { repoPath } from "../helpers/repo-root"; async function waitForOutput( stream: ReadableStream, @@ -31,7 +32,7 @@ describe("ocx init piped stdin (#754)", () => { test("exits cleanly when stdin closes before the first prompt answer", async () => { const home = mkdtempSync(join(tmpdir(), "ocx-init-eof-")); dirs.push(home); - const cli = join(import.meta.dir, "..", "src", "cli", "index.ts"); + const cli = repoPath("src", "cli", "index.ts"); const proc = Bun.spawn({ cmd: [process.execPath, cli, "init"], env: { ...process.env, OPENCODEX_HOME: home }, diff --git a/tests/process-state.test.ts b/tests/service/process-state.test.ts similarity index 95% rename from tests/process-state.test.ts rename to tests/service/process-state.test.ts index 59c750cee7..21d27a1e3a 100644 --- a/tests/process-state.test.ts +++ b/tests/service/process-state.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { delimiter, dirname, join } from "node:path"; -import * as configFacade from "../src/config"; +import * as configFacade from "../../src/config"; import { getPidPath, getRuntimePortPath, @@ -18,9 +18,10 @@ import { setProcessCommandLinePlatformForTests, writePid, writeRuntimePort, -} from "../src/config/process-state"; -import { setTrustedWindowsSystemDirectoryResolverForTests } from "../src/lib/windows-elevation"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +} from "../../src/config/process-state"; +import { setTrustedWindowsSystemDirectoryResolverForTests } from "../../src/lib/windows-elevation"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { repoPath } from "../helpers/repo-root"; let testDir = ""; @@ -42,7 +43,7 @@ afterEach(() => { describe("proxy process-state ownership", () => { test("the process-state leaf does not import the config facade", () => { - const source = readFileSync(join(import.meta.dir, "..", "src", "config", "process-state.ts"), "utf-8"); + const source = readFileSync(repoPath("src", "config", "process-state.ts"), "utf-8"); expect(source).not.toMatch(/from\s+["']\.\.\/config["']/); expect(source).toContain('from "./atomic-write"'); expect(source).toContain('from "./paths"'); diff --git a/tests/service-probe-docker.test.ts b/tests/service/service-probe-docker.test.ts similarity index 87% rename from tests/service-probe-docker.test.ts rename to tests/service/service-probe-docker.test.ts index 2cb8fb66c1..e5bbbbf4b8 100644 --- a/tests/service-probe-docker.test.ts +++ b/tests/service/service-probe-docker.test.ts @@ -6,8 +6,8 @@ import { join } from "node:path"; import { inspectServiceManagerInstallation, type ProbeRunner, -} from "../src/service-manager-probe"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +} from "../../src/service-manager-probe"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; test("Linux reports systemd absent when systemctl cannot be spawned", () => { const home = mkdtempSync(join(tmpdir(), "ocx-probe-docker-")); diff --git a/tests/service-secrets.test.ts b/tests/service/service-secrets.test.ts similarity index 98% rename from tests/service-secrets.test.ts rename to tests/service/service-secrets.test.ts index 6ac04721f2..029cd6d7f2 100644 --- a/tests/service-secrets.test.ts +++ b/tests/service/service-secrets.test.ts @@ -23,8 +23,8 @@ import { serviceApiTokenFingerprint, writeServiceApiTokenFile, writeTokenBackup, -} from "../src/lib/service-secrets"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +} from "../../src/lib/service-secrets"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; let home = ""; diff --git a/tests/service-stop-verification.test.ts b/tests/service/service-stop-verification.test.ts similarity index 98% rename from tests/service-stop-verification.test.ts rename to tests/service/service-stop-verification.test.ts index 53f05f34f4..5f228b9e2a 100644 --- a/tests/service-stop-verification.test.ts +++ b/tests/service/service-stop-verification.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { proxyStillLiveAfterStop } from "../src/service"; +import { proxyStillLiveAfterStop } from "../../src/service"; /** * #764: `ocx service stop` reported success while the proxy kept running, and native Codex was diff --git a/tests/service-tier-capability.test.ts b/tests/service/service-tier-capability.test.ts similarity index 96% rename from tests/service-tier-capability.test.ts rename to tests/service/service-tier-capability.test.ts index fd1a418602..4f8161c3e9 100644 --- a/tests/service-tier-capability.test.ts +++ b/tests/service/service-tier-capability.test.ts @@ -7,14 +7,14 @@ * ever receiving an injection (PR #860 family). */ import { afterEach, describe, expect, test } from "bun:test"; -import { applyProviderConfigHints, buildCatalogEntries, gatherRoutedModels } from "../src/codex/catalog"; -import { applyCatalogModelMetadata } from "../src/codex/catalog/effort"; -import type { RawEntry } from "../src/codex/catalog/parsing"; -import { providerConfigSeed, enrichProviderFromRegistry } from "../src/providers/derive"; -import { getProviderRegistryEntry } from "../src/providers/registry"; -import { decideTier } from "../src/providers/fastwire"; -import type { RequestLogContext } from "../src/server/request-log"; -import { applyServiceTierGate, handleResponses } from "../src/server/responses/core"; +import { applyProviderConfigHints, buildCatalogEntries, gatherRoutedModels } from "../../src/codex/catalog"; +import { applyCatalogModelMetadata } from "../../src/codex/catalog/effort"; +import type { RawEntry } from "../../src/codex/catalog/parsing"; +import { providerConfigSeed, enrichProviderFromRegistry } from "../../src/providers/derive"; +import { getProviderRegistryEntry } from "../../src/providers/registry"; +import { decideTier } from "../../src/providers/fastwire"; +import type { RequestLogContext } from "../../src/server/request-log"; +import { applyServiceTierGate, handleResponses } from "../../src/server/responses/core"; import { canForwardServiceTierForModel, fastPolicyForModel, @@ -22,10 +22,10 @@ import { serviceTierSupportForModel, serviceTierSupportFromPolicy, supportsServiceTierForModel, -} from "../src/providers/service-tier"; -import { candidateCapabilityEvidence } from "../src/routing/capability"; -import { resolveProductionBehaviorValues } from "../src/routing/compatibility/behavior"; -import type { OcxConfig, OcxProviderConfig } from "../src/types"; +} from "../../src/providers/service-tier"; +import { candidateCapabilityEvidence } from "../../src/routing/capability"; +import { resolveProductionBehaviorValues } from "../../src/routing/compatibility/behavior"; +import type { OcxConfig, OcxProviderConfig } from "../../src/types"; describe("registry capability reaches saved configs without overriding them", () => { test("the registry holds the defaults; the seed stays free of them so explicit config stays distinguishable", () => { diff --git a/tests/service.test.ts b/tests/service/service.test.ts similarity index 99% rename from tests/service.test.ts rename to tests/service/service.test.ts index fca80972cc..8c9d10cb8a 100644 --- a/tests/service.test.ts +++ b/tests/service/service.test.ts @@ -4,20 +4,21 @@ import { execFileSync } from "node:child_process"; import { tmpdir } from "node:os"; import { delimiter, isAbsolute, join, posix, win32 } from "node:path"; import { pathToFileURL } from "node:url"; -import * as serviceModule from "../src/service"; -import { saveConfig } from "../src/config"; -import { windowsEnvIndirectBatchValue } from "../src/lib/win-paths"; -import { assertServiceAuthEnvironment, assertServiceEnvironmentMatchesInstall, bakedServicePathsDiagnostic, confirmServiceServing, launchdListenPort, systemdListenPort, buildPlist, buildUnit, buildWindowsLauncherVbs, buildWindowsSchtasksCreateArgs, buildWindowsSchtasksCreateArgsForXml, buildWindowsServiceScript, buildWindowsTaskXml as buildWindowsTaskXmlProduction, buildWindowsTaskXmlDocument, deriveWindowsServiceDiagnostic, deriveWindowsServiceDiagnosticForCurrentUser, installFreshWindowsSchedulerSafely, installServiceSafely, launchctlLoadFailed, launchdJobMatchesPlist, normalizeServiceSubcommand, parseServiceArgs, parseServiceInstallState, planServiceCommand, prepareServiceInstall, probeServiceInstallation, readWindowsSchedulerXmlState, registerFreshWindowsSchedulerTask, removeNativeWindowsServiceForScheduler, repairService, reportServiceServing, resolveServiceListenPort, runLaunchctl, selectServiceSubcommand, SERVICE_INSTALL_HEALTH_MS, SERVICE_INSTALL_HEALTH_WINDOWS_MS, serviceInstallHealthMs, serviceLogPath, serviceStartableFromTray, serviceStatusReport, serviceRetryCommand, serviceStatusSummary, stableLauncherEntry, systemdNeedsDaemonReload, systemdServiceInstallCleanupOps, uninstallSystemd, windowsListenPort, winswListenPort, startLaunchd, windowsTaskRegistrationHealthy as windowsTaskRegistrationHealthyProduction } from "../src/service"; -import type { ServiceDiagnostic } from "../src/service"; -import { definitionCarriesCredential, resolvedProxyEnv, writeServiceDefinitionFile } from "../src/service"; -import { buildWinswXml } from "../src/lib/winsw"; -import { CONFIG_OWNER_FILE, CONFIG_UNINSTALL_MANIFEST, recordOwnedConfigPath, removeOwnedConfigState } from "../src/lib/config-ownership"; -import { serviceApiTokenFilePath } from "../src/lib/service-secrets"; -import { WindowsSchtasksError } from "../src/lib/windows-elevation"; -import { resolveCurrentWindowsPrincipal, setWindowsPrincipalRunnerForTests } from "../src/lib/windows-user-principal"; -import { setAsyncIcaclsRunnerForTests, setIcaclsRunnerForTests } from "../src/lib/windows-secret-acl"; -import type { OcxConfig } from "../src/types"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +import * as serviceModule from "../../src/service"; +import { saveConfig } from "../../src/config"; +import { windowsEnvIndirectBatchValue } from "../../src/lib/win-paths"; +import { assertServiceAuthEnvironment, assertServiceEnvironmentMatchesInstall, bakedServicePathsDiagnostic, confirmServiceServing, launchdListenPort, systemdListenPort, buildPlist, buildUnit, buildWindowsLauncherVbs, buildWindowsSchtasksCreateArgs, buildWindowsSchtasksCreateArgsForXml, buildWindowsServiceScript, buildWindowsTaskXml as buildWindowsTaskXmlProduction, buildWindowsTaskXmlDocument, deriveWindowsServiceDiagnostic, deriveWindowsServiceDiagnosticForCurrentUser, installFreshWindowsSchedulerSafely, installServiceSafely, launchctlLoadFailed, launchdJobMatchesPlist, normalizeServiceSubcommand, parseServiceArgs, parseServiceInstallState, planServiceCommand, prepareServiceInstall, probeServiceInstallation, readWindowsSchedulerXmlState, registerFreshWindowsSchedulerTask, removeNativeWindowsServiceForScheduler, repairService, reportServiceServing, resolveServiceListenPort, runLaunchctl, selectServiceSubcommand, SERVICE_INSTALL_HEALTH_MS, SERVICE_INSTALL_HEALTH_WINDOWS_MS, serviceInstallHealthMs, serviceLogPath, serviceStartableFromTray, serviceStatusReport, serviceRetryCommand, serviceStatusSummary, stableLauncherEntry, systemdNeedsDaemonReload, systemdServiceInstallCleanupOps, uninstallSystemd, windowsListenPort, winswListenPort, startLaunchd, windowsTaskRegistrationHealthy as windowsTaskRegistrationHealthyProduction } from "../../src/service"; +import type { ServiceDiagnostic } from "../../src/service"; +import { definitionCarriesCredential, resolvedProxyEnv, writeServiceDefinitionFile } from "../../src/service"; +import { buildWinswXml } from "../../src/lib/winsw"; +import { CONFIG_OWNER_FILE, CONFIG_UNINSTALL_MANIFEST, recordOwnedConfigPath, removeOwnedConfigState } from "../../src/lib/config-ownership"; +import { serviceApiTokenFilePath } from "../../src/lib/service-secrets"; +import { WindowsSchtasksError } from "../../src/lib/windows-elevation"; +import { resolveCurrentWindowsPrincipal, setWindowsPrincipalRunnerForTests } from "../../src/lib/windows-user-principal"; +import { setAsyncIcaclsRunnerForTests, setIcaclsRunnerForTests } from "../../src/lib/windows-secret-acl"; +import type { OcxConfig } from "../../src/types"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { repoRoot } from "../helpers/repo-root"; const TEST_WINDOWS_TASK_SID = "S-1-5-21-111-222-333-1001"; // The synthetic SID above exists nowhere. On a real Windows host every saveConfig() in this @@ -59,7 +60,7 @@ afterEach(() => { if (existsSync(TEST_DIR)) removeTreeWithRetry(TEST_DIR); }); -const root = new URL("../", import.meta.url); +const root = pathToFileURL(repoRoot() + "/"); async function readText(path: string): Promise { return await Bun.file(new URL(path, root)).text(); @@ -891,7 +892,7 @@ describe("Windows service task", () => { }); test("writes the launcher VBS with a UTF-16 BOM so non-ASCII paths survive WSH decoding", async () => { - const service = await Bun.file(new URL("../src/service.ts", import.meta.url)).text(); + const service = await Bun.file(new URL("../../src/service.ts", import.meta.url)).text(); expect(service).toContain('writeServiceAssetWithRetry(windowsLauncherVbsPath(), `\\uFEFF${buildWindowsLauncherVbs(script)}`, "utf16le")'); // Uninstall must clean the launcher asset alongside the script and task XML. diff --git a/tests/shutdown-drain.test.ts b/tests/service/shutdown-drain.test.ts similarity index 98% rename from tests/shutdown-drain.test.ts rename to tests/service/shutdown-drain.test.ts index bc3829a637..dd3d1e31cd 100644 --- a/tests/shutdown-drain.test.ts +++ b/tests/service/shutdown-drain.test.ts @@ -12,7 +12,7 @@ import { trackStreamLifetime, isRecyclingForExit, markRecyclingForExit, -} from "../src/server"; +} from "../../src/server"; import { acquireTemporaryDrain, acquireNativeMainProfileDrain, @@ -25,14 +25,14 @@ import { tryAdmitTurn, codexAccountSelectionForTurn, getNativeMainProfileRequestCount, -} from "../src/server/lifecycle"; +} from "../../src/server/lifecycle"; import { backgroundShellAdmissionMetrics, backgroundShellSpawnExec, resetBackgroundShellStateForTests, setBackgroundShellRuntimeForTests, -} from "../src/adapters/cursor/native-exec-shell"; -import { BackgroundShellSpawnArgsSchema, ExecServerMessageSchema } from "../src/adapters/cursor/gen/agent_pb"; +} from "../../src/adapters/cursor/native-exec-shell"; +import { BackgroundShellSpawnArgsSchema, ExecServerMessageSchema } from "../../src/adapters/cursor/gen/agent_pb"; class ShutdownFakeChild extends EventEmitter { readonly stdin = new PassThrough(); diff --git a/tests/shutdown-launcher.test.ts b/tests/service/shutdown-launcher.test.ts similarity index 97% rename from tests/shutdown-launcher.test.ts rename to tests/service/shutdown-launcher.test.ts index 189629988e..edda9ecb5b 100644 --- a/tests/shutdown-launcher.test.ts +++ b/tests/service/shutdown-launcher.test.ts @@ -4,8 +4,9 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from import { createServer } from "node:net"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { claimOwnedServiceHome } from "./helpers/owned-service-home"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +import { claimOwnedServiceHome } from "../helpers/owned-service-home"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { repoPath } from "../helpers/repo-root"; /** * Regression: `ocx start` + Ctrl-C must NOT orphan the Bun proxy. @@ -20,7 +21,7 @@ import { removeTreeWithRetry } from "./helpers/remove-tree"; * on PATH to exercise the real launcher. */ -const BIN_OCX = join(import.meta.dir, "..", "bin", "ocx.mjs"); +const BIN_OCX = repoPath("bin", "ocx.mjs"); const nodeAvailable = !spawnSync("node", ["--version"], { stdio: "ignore" }).error; const runnable = process.platform !== "win32" && nodeAvailable; diff --git a/tests/stale-state-purge.test.ts b/tests/service/stale-state-purge.test.ts similarity index 89% rename from tests/stale-state-purge.test.ts rename to tests/service/stale-state-purge.test.ts index 856da2dc53..ceac7e5c77 100644 --- a/tests/stale-state-purge.test.ts +++ b/tests/service/stale-state-purge.test.ts @@ -1,7 +1,8 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { repoPath } from "../helpers/repo-root"; const TEST_DIR = join(import.meta.dir, ".tmp-stale-state-purge-test"); let prevOpencodexHome: string | undefined; @@ -21,7 +22,7 @@ describe("snapshot-guarded stale-state purge", () => { }); test("removePidIfValueIs deletes only when the file still matches the snapshot", async () => { - const { getPidPath, removePidIfValueIs } = await import("../src/config"); + const { getPidPath, removePidIfValueIs } = await import("../../src/config"); writeFileSync(getPidPath(), "123"); removePidIfValueIs(999); // concurrent start rewrote the file since the snapshot @@ -35,7 +36,7 @@ describe("snapshot-guarded stale-state purge", () => { }); test("removeRuntimePortIfPidIs deletes matching and invalid-snapshot records, keeps fresh ones", async () => { - const { getConfigDir, removeRuntimePortIfPidIs } = await import("../src/config"); + const { getConfigDir, removeRuntimePortIfPidIs } = await import("../../src/config"); const runtimePath = join(getConfigDir(), "runtime-port.json"); writeFileSync(runtimePath, JSON.stringify({ pid: 42, port: 58195 })); @@ -52,7 +53,7 @@ describe("snapshot-guarded stale-state purge", () => { }); test("handleStop snapshots stale state before probing and purges through the guards", () => { - const cliSource = readFileSync(join(import.meta.dir, "..", "src", "cli", "index.ts"), "utf8"); + const cliSource = readFileSync(repoPath("src", "cli", "index.ts"), "utf8"); const stopFn = cliSource.slice(cliSource.indexOf("async function handleStop()"), cliSource.indexOf("async function handleUninstall()")); const snapshotAt = stopFn.indexOf("const stalePidValue = readPidFileValue()"); @@ -67,8 +68,8 @@ describe("snapshot-guarded stale-state purge", () => { }); test("gui opens the actual bind host and recover-history surfaces a locked DB", () => { - const cliSource = readFileSync(join(import.meta.dir, "..", "src", "cli", "index.ts"), "utf8"); - const dispatchSource = readFileSync(join(import.meta.dir, "..", "src", "cli", "dispatch.ts"), "utf8"); + const cliSource = readFileSync(repoPath("src", "cli", "index.ts"), "utf8"); + const dispatchSource = readFileSync(repoPath("src", "cli", "dispatch.ts"), "utf8"); expect(dispatchSource).toContain("const guiHost = deps.probeHostname(live?.hostname ?? config.hostname)"); const recoverFn = cliSource.slice(cliSource.indexOf("function handleRecoverHistory()"), cliSource.indexOf("await dispatchCommand(head")); expect(recoverFn).toContain("if (r.failed)"); diff --git a/tests/stop-deferred-teardown.test.ts b/tests/service/stop-deferred-teardown.test.ts similarity index 93% rename from tests/stop-deferred-teardown.test.ts rename to tests/service/stop-deferred-teardown.test.ts index 12a6f6003d..14ba8987d5 100644 --- a/tests/stop-deferred-teardown.test.ts +++ b/tests/service/stop-deferred-teardown.test.ts @@ -2,10 +2,10 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { stopProxyGracefully } from "../src/lib/process-control"; -import { performStopTeardown } from "../src/server/stop-teardown"; -import type { CodexNativeRestoreResult } from "../src/codex/inject"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +import { stopProxyGracefully } from "../../src/lib/process-control"; +import { performStopTeardown } from "../../src/server/stop-teardown"; +import type { CodexNativeRestoreResult } from "../../src/codex/inject"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; /** * Behavioural cover for the deferred shared teardown (#3008). @@ -128,7 +128,7 @@ describe("performStopTeardown", () => { }); test("the real ownership check accepts only a nonce with a readable receipt on disk", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); let restored = 0; const deferred = await performStopTeardown(new URL(`http://127.0.0.1:10100/api/stop?deferSharedTeardown=1&teardownNonce=${claimed.nonce}`), { @@ -162,7 +162,7 @@ describe("performStopTeardown", () => { }); test("an unreadable receipt does not authorize a deferral", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); writeFileSync(mod.pendingTeardownPathFor(claimed.nonce), "{not json"); let restored = 0; @@ -213,8 +213,8 @@ describe("performStopTeardown", () => { describe("receipt naming is shared by both update lanes", () => { test("the launcher's scan and the TypeScript listing agree on what is outstanding", async () => { - const mod = await import("../src/config/pending-teardown"); - const names = await import("../src/config/pending-teardown-names.mjs"); + const mod = await import("../../src/config/pending-teardown"); + const names = await import("../../src/config/pending-teardown-names.mjs"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); // bin/ocx.mjs runs under plain Node and cannot import the TypeScript module, so the @@ -247,7 +247,7 @@ describe("receipt naming is shared by both update lanes", () => { }); test("a scan that fails is not an empty scan", async () => { - const names = await import("../src/config/pending-teardown-names.mjs"); + const names = await import("../../src/config/pending-teardown-names.mjs"); // Only a missing home is honestly empty. Any other failure may be hiding an // obligation, and reporting "none" would let an update install over a teardown that // never ran — absence of proof is not proof of absence. @@ -259,7 +259,7 @@ describe("receipt naming is shared by both update lanes", () => { }); test("a home that cannot be scanned is its own state, not a fabricated receipt", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const previous = process.env.OPENCODEX_HOME; // A file where the home should be: readdir fails with ENOTDIR, which is not absence. const notADir = join(home, "not-a-directory"); @@ -284,7 +284,7 @@ describe("receipt naming is shared by both update lanes", () => { describe("endpoint provenance", () => { test("a guessed endpoint is recorded as such and is not exact evidence", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const guessed = mod.claimPendingTeardown({ hostname: "127.0.0.1", port: 10100 }, "guessed", 1234); const read = mod.readPendingTeardown(guessed.nonce); expect(read.state === "valid" && read.receipt.endpointSource).toBe("guessed"); @@ -297,7 +297,7 @@ describe("endpoint provenance", () => { }); test("a receipt without provenance is invalid, so an old-format file cannot be trusted", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); writeFileSync( mod.pendingTeardownPathFor(claimed.nonce), @@ -314,7 +314,7 @@ describe("endpoint provenance", () => { describe("post-stop update decision", () => { test("an outstanding obligation aborts the install even when the stop succeeded", async () => { - const { decidePostStopUpdate } = await import("../src/update/stop-decision.mjs"); + const { decidePostStopUpdate } = await import("../../src/update/stop-decision.mjs"); // A quarantined receipt lets the stop itself succeed — there is nothing left to stop — // so checking only BEFORE the stop let the retry sail through and install over a // teardown that never ran. @@ -333,7 +333,7 @@ describe("post-stop update decision", () => { describe("pending teardown receipts", () => { test("a claim is durable and carries the endpoint it was stopping", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); expect(claimed.nonce).toMatch(/^[0-9a-f]{32}$/); expect(existsSync(mod.pendingTeardownPathFor(claimed.nonce))).toBe(true); @@ -344,7 +344,7 @@ describe("pending teardown receipts", () => { }); test("a clear names one obligation, so a concurrent claim cannot be deleted by it", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); // Review round 8 reproduced the delete-the-wrong-receipt bug; round 10 pointed out // that a read-compare-unlink against ONE shared path is still racy, because the file // can be replaced between the compare and the unlink. The nonce is the filename now, @@ -361,7 +361,7 @@ describe("pending teardown receipts", () => { }); test("clearing reports whether the obligation is actually gone", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); expect(mod.clearPendingTeardown(claimed.nonce)).toBe(true); // Already gone is still "gone" — an idempotent discharge is not a failure. @@ -376,7 +376,7 @@ describe("pending teardown receipts", () => { }); test("an unreadable receipt is invalid, outstanding, and quarantinable", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); writeFileSync(mod.pendingTeardownPathFor(claimed.nonce), "{not json"); const read = mod.readPendingTeardown(claimed.nonce); @@ -395,7 +395,7 @@ describe("pending teardown receipts", () => { }); test("a directory where a receipt belongs is invalid, not missing", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); rmSync(mod.pendingTeardownPathFor(claimed.nonce)); mkdirSync(mod.pendingTeardownPathFor(claimed.nonce), { recursive: true }); @@ -406,7 +406,7 @@ describe("pending teardown receipts", () => { }); test("a receipt whose body disagrees with its filename is invalid", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); writeFileSync( mod.pendingTeardownPathFor(claimed.nonce), @@ -418,7 +418,7 @@ describe("pending teardown receipts", () => { }); test("a receipt without a usable endpoint is invalid, because recovery could not locate it", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); const path = mod.pendingTeardownPathFor(claimed.nonce); const base = { ownerPid: 7, nonce: claimed.nonce, createdAt: "t", endpointSource: "exact" }; @@ -431,7 +431,7 @@ describe("pending teardown receipts", () => { }); test("only an abandoned receipt is recoverable", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 4242); const live = mod.readPendingTeardown(claimed.nonce); @@ -446,7 +446,7 @@ describe("pending teardown receipts", () => { }); test("deferralMatchesReceipt needs a well-formed nonce that names a readable receipt", async () => { - const mod = await import("../src/config/pending-teardown"); + const mod = await import("../../src/config/pending-teardown"); const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 7); expect(mod.deferralMatchesReceipt(claimed.nonce)).toBe(true); expect(mod.deferralMatchesReceipt(FOREIGN_NONCE)).toBe(false); diff --git a/tests/systemd-install-cleanup-hardening.test.ts b/tests/service/systemd-install-cleanup-hardening.test.ts similarity index 83% rename from tests/systemd-install-cleanup-hardening.test.ts rename to tests/service/systemd-install-cleanup-hardening.test.ts index a3c5d288e2..831faf0f19 100644 --- a/tests/systemd-install-cleanup-hardening.test.ts +++ b/tests/service/systemd-install-cleanup-hardening.test.ts @@ -1,8 +1,9 @@ import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; +import { repoPath } from "../helpers/repo-root"; -const source = readFileSync(join(import.meta.dir, "../src/service.ts"), "utf8"); +const source = readFileSync(repoPath("src/service.ts"), "utf8"); describe("systemd install cleanup status hardening", () => { test("only treats literal not-found as confirmed unit absence", () => { diff --git a/tests/winsw.test.ts b/tests/service/winsw.test.ts similarity index 96% rename from tests/winsw.test.ts rename to tests/service/winsw.test.ts index 7fd59918a9..3366c6cbc3 100644 --- a/tests/winsw.test.ts +++ b/tests/service/winsw.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test } from "bun:test"; -import { buildWinswXml, ensureWinswBinary, parseWinswStatus, probeScmRegistration, sha256Hex, installWinswService, statusWinswRaw, WINSW_SHA256, WINSW_SERVICE_ID } from "../src/lib/winsw"; -import { parseServiceArgs, serviceInstallArgs, serviceReinstallArgs } from "../src/service"; -import { loadServiceTokenFromFile } from "../src/lib/service-secrets"; -import { getConfigDir } from "../src/config"; +import { buildWinswXml, ensureWinswBinary, parseWinswStatus, probeScmRegistration, sha256Hex, installWinswService, statusWinswRaw, WINSW_SHA256, WINSW_SERVICE_ID } from "../../src/lib/winsw"; +import { parseServiceArgs, serviceInstallArgs, serviceReinstallArgs } from "../../src/service"; +import { loadServiceTokenFromFile } from "../../src/lib/service-secrets"; +import { getConfigDir } from "../../src/config"; import { mkdtempSync, readFileSync, writeFileSync} from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; const entry = { bun: "C:\\OpenCodex\\bun.exe", bunRuntimeSource: "bundled" as const, cli: "C:\\Open Codex\\cli & co\\index.ts" }; @@ -151,7 +151,7 @@ describe("winsw fail-closed lifecycle", () => { } // On win32 the same branch must confirm against the SCM — a quarantined/deleted // exe does not prove the registration is gone. - const winsw = readFileSync(new URL("../src/lib/winsw.ts", import.meta.url), "utf8"); + const winsw = readFileSync(new URL("../../src/lib/winsw.ts", import.meta.url), "utf8"); const fn = winsw.slice(winsw.indexOf("export function statusWinswRaw"), winsw.indexOf("/**", winsw.indexOf("export function statusWinswRaw"))); expect(fn).toContain('process.platform !== "win32"'); expect(fn).toContain("probeScmRegistration()"); @@ -177,7 +177,7 @@ describe("winsw fail-closed lifecycle", () => { }); test("uninstall removes a stale SCM registration via sc.exe when the exe is gone", () => { - const winsw = readFileSync(new URL("../src/lib/winsw.ts", import.meta.url), "utf8"); + const winsw = readFileSync(new URL("../../src/lib/winsw.ts", import.meta.url), "utf8"); const fn = winsw.slice(winsw.indexOf("export function uninstallWinswService"), winsw.indexOf("export function winswStatusSummary")); expect(fn).toContain("!existsSync(winswExePath())"); expect(fn).toContain("probeScmRegistration()"); @@ -204,7 +204,7 @@ describe("winsw install flow", () => { }); test("install /p refuses non-interactive stdin instead of hanging", () => { - const winsw = readFileSync(new URL("../src/lib/winsw.ts", import.meta.url), "utf8"); + const winsw = readFileSync(new URL("../../src/lib/winsw.ts", import.meta.url), "utf8"); const fn = winsw.slice(winsw.indexOf("function runWinswInteractive"), winsw.indexOf("function scQc()")); expect(fn).toContain("process.stdin.isTTY"); expect(fn).toContain("interactive console"); diff --git a/tests/update-badge.test.ts b/tests/update/update-badge.test.ts similarity index 93% rename from tests/update-badge.test.ts rename to tests/update/update-badge.test.ts index eb668e27f9..b290b7615c 100644 --- a/tests/update-badge.test.ts +++ b/tests/update/update-badge.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; -import { readUpdateBadge, type UpdateBadgeDeps } from "../src/update/badge"; -import type { Channel } from "../src/update/index"; -import type { VersionCache } from "../src/update/notify"; +import { readUpdateBadge, type UpdateBadgeDeps } from "../../src/update/badge"; +import type { Channel } from "../../src/update/index"; +import type { VersionCache } from "../../src/update/notify"; function deps(overrides: { current?: string; diff --git a/tests/update-job.test.ts b/tests/update/update-job.test.ts similarity index 99% rename from tests/update-job.test.ts rename to tests/update/update-job.test.ts index 7c11d8dc59..6e02ecd575 100644 --- a/tests/update-job.test.ts +++ b/tests/update/update-job.test.ts @@ -18,9 +18,9 @@ import { updateExecutionCommand, updateJobPath, type UpdateJobState, -} from "../src/update/job"; -import { checkUpdatePackageIntegrity, updateCommand, updateCommandStr } from "../src/update/index"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +} from "../../src/update/job"; +import { checkUpdatePackageIntegrity, updateCommand, updateCommandStr } from "../../src/update/index"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; type SpawnResult = { status: number | null; stdout: string }; function fakeSpawn(result: SpawnResult): typeof import("node:child_process").spawnSync { @@ -1603,7 +1603,7 @@ describe("immutable update target (WP160)", () => { }); test("GUI worker gates integrity before spawning and fails the job on anomalous metadata", async () => { - const source = await Bun.file(new URL("../src/update/job.ts", import.meta.url)).text(); + const source = await Bun.file(new URL("../../src/update/job.ts", import.meta.url)).text(); const gateAt = source.indexOf("const integrity = (io.integrityFn ?? checkUpdatePackageIntegrity)(check.latestVersion);"); const cacheGateAt = source.indexOf("const cachePreflight = (io.cachePreflightFn ?? runNpmCachePreflight)();"); diff --git a/tests/update-notify.test.ts b/tests/update/update-notify.test.ts similarity index 96% rename from tests/update-notify.test.ts rename to tests/update/update-notify.test.ts index a004d4cff4..5f1509f716 100644 --- a/tests/update-notify.test.ts +++ b/tests/update/update-notify.test.ts @@ -10,8 +10,10 @@ import { readVersionCache, writeVersionCache, type VersionCache, -} from "../src/update/notify"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +} from "../../src/update/notify"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { repoRoot } from "../helpers/repo-root"; +import { pathToFileURL } from "node:url"; const prevHome = process.env.OPENCODEX_HOME; let dir: string; @@ -119,7 +121,7 @@ describe("getUpgradeVersionForPopup", () => { }); describe("cli wiring", () => { - const root = new URL("../", import.meta.url); + const root = pathToFileURL(repoRoot() + "/"); const readText = (p: string) => Bun.file(new URL(p, root)).text(); test("update prompt runs before the server binds a port", async () => { diff --git a/tests/update-npm-cache-preflight.test.ts b/tests/update/update-npm-cache-preflight.test.ts similarity index 99% rename from tests/update-npm-cache-preflight.test.ts rename to tests/update/update-npm-cache-preflight.test.ts index 681351379d..0460f66921 100644 --- a/tests/update-npm-cache-preflight.test.ts +++ b/tests/update/update-npm-cache-preflight.test.ts @@ -5,8 +5,8 @@ import { join } from "node:path"; import { inspectNpmCacheDirectory, runNpmCachePreflight, -} from "../src/update/npm-cache-preflight.mjs"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +} from "../../src/update/npm-cache-preflight.mjs"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; const roots: string[] = []; diff --git a/tests/update-npm-invocation.test.ts b/tests/update/update-npm-invocation.test.ts similarity index 98% rename from tests/update-npm-invocation.test.ts rename to tests/update/update-npm-invocation.test.ts index 6a09569060..a8020d797e 100644 --- a/tests/update-npm-invocation.test.ts +++ b/tests/update/update-npm-invocation.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { npmInvocation, resolveNpmCommand, -} from "../src/update/npm-invocation.mjs"; +} from "../../src/update/npm-invocation.mjs"; const cwd = "C:\\work\\untrusted-project"; const trustedNpm = "C:\\Program Files\\nodejs\\npm.cmd"; diff --git a/tests/update-stop-classification.test.ts b/tests/update/update-stop-classification.test.ts similarity index 97% rename from tests/update-stop-classification.test.ts rename to tests/update/update-stop-classification.test.ts index d9d4c73349..722bf584fe 100644 --- a/tests/update-stop-classification.test.ts +++ b/tests/update/update-stop-classification.test.ts @@ -2,11 +2,12 @@ import { describe, expect, test } from "bun:test"; import { spawn } from "node:child_process"; import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "../src/update/stop-contract.mjs"; -import { probeProxyLiveness } from "../src/update/proxy-liveness-probe.mjs"; -import { decidePostStopUpdate } from "../src/update/stop-decision.mjs"; +import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "../../src/update/stop-contract.mjs"; +import { probeProxyLiveness } from "../../src/update/proxy-liveness-probe.mjs"; +import { decidePostStopUpdate } from "../../src/update/stop-decision.mjs"; +import { repoRoot as resolveRepoRoot } from "../helpers/repo-root"; -const repoRoot = join(import.meta.dir, ".."); +const repoRoot = resolveRepoRoot(); const read = (rel: string): string => readFileSync(join(repoRoot, rel), "utf8"); /** diff --git a/tests/update-stop-first.test.ts b/tests/update/update-stop-first.test.ts similarity index 96% rename from tests/update-stop-first.test.ts rename to tests/update/update-stop-first.test.ts index 95b6bd53da..217153ebdb 100644 --- a/tests/update-stop-first.test.ts +++ b/tests/update/update-stop-first.test.ts @@ -3,11 +3,12 @@ import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSy import { createServer } from "node:net"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; -import { runNpmCachePreflight } from "../src/update/npm-cache-preflight.mjs"; -import { isProcessAlive, killProxy } from "../src/lib/process-control"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +import { runNpmCachePreflight } from "../../src/update/npm-cache-preflight.mjs"; +import { isProcessAlive, killProxy } from "../../src/lib/process-control"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { repoRoot as resolveRepoRoot } from "../helpers/repo-root"; -const repoRoot = join(import.meta.dir, ".."); +const repoRoot = resolveRepoRoot(); function freePort(): Promise { const { promise, resolve, reject } = Promise.withResolvers(); @@ -61,10 +62,10 @@ async function waitForProxy(port: number): Promise { } return false; } -const updateSource = readFileSync(join(import.meta.dir, "..", "src", "update", "index.ts"), "utf8"); -const launcherSource = readFileSync(join(import.meta.dir, "..", "bin", "ocx.mjs"), "utf8"); -const serverSource = readFileSync(join(import.meta.dir, "..", "src", "server", "index.ts"), "utf8"); -const dispatchSource = readFileSync(join(import.meta.dir, "..", "src", "cli", "dispatch.ts"), "utf8"); +const updateSource = readFileSync(join(repoRoot, "src", "update", "index.ts"), "utf8"); +const launcherSource = readFileSync(join(repoRoot, "bin", "ocx.mjs"), "utf8"); +const serverSource = readFileSync(join(repoRoot, "src", "server", "index.ts"), "utf8"); +const dispatchSource = readFileSync(join(repoRoot, "src", "cli", "dispatch.ts"), "utf8"); describe("update stops the running proxy before replacing files", () => { // The recovery case starts a real detached proxy, and its own result says nothing about @@ -330,7 +331,7 @@ esac expect(launcherSource).toContain("if (serviceWasInstalled || hasRuntimeState || hasPendingTeardown)"); // The rule now lives in the shared post-stop decision both lanes import (#3008): a // history-only stop proceeds, every other nonzero status and any surviving runtime - // state aborts. Pinned by tests/update-stop-classification.test.ts. + // state aborts. Pinned by tests/update/update-stop-classification.test.ts. expect(launcherSource).toContain("decidePostStopUpdate({"); expect(launcherSource).toContain("hasRuntimeState: stillHasRuntimeState"); }); diff --git a/tests/update-transactional.test.ts b/tests/update/update-transactional.test.ts similarity index 98% rename from tests/update-transactional.test.ts rename to tests/update/update-transactional.test.ts index 6986d9aea9..9f8c95e27c 100644 --- a/tests/update-transactional.test.ts +++ b/tests/update/update-transactional.test.ts @@ -10,8 +10,8 @@ import { bootRestoreProbe, transactionalNpmUpdate, verifyInstallTree, -} from "../src/update/transactional-install.mjs"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +} from "../../src/update/transactional-install.mjs"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; const PKG = "@bitkyc08/opencodex"; diff --git a/tests/update-tray-handoff.test.ts b/tests/update/update-tray-handoff.test.ts similarity index 94% rename from tests/update-tray-handoff.test.ts rename to tests/update/update-tray-handoff.test.ts index 7945ccd95a..6250e591da 100644 --- a/tests/update-tray-handoff.test.ts +++ b/tests/update/update-tray-handoff.test.ts @@ -1,7 +1,8 @@ import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate, windowsTrayStopConfirmed } from "../src/update/tray-update-plan.mjs"; +import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate, windowsTrayStopConfirmed } from "../../src/update/tray-update-plan.mjs"; +import { repoRoot } from "../helpers/repo-root"; describe("Windows tray update handoff contract", () => { test("preserves an installed, running tray across replacement", () => { @@ -51,7 +52,7 @@ describe("Windows tray update handoff contract", () => { }); test("all three updater lanes consume the same tested plan", () => { - const root = join(import.meta.dir, ".."); + const root = repoRoot(); for (const path of ["src/update/index.ts", "src/update/job.ts", "bin/ocx.mjs"]) { const source = readFileSync(join(root, path), "utf8"); expect(source).toContain("planWindowsTrayUpdate"); diff --git a/tests/tray-proxy-deadline.test.ts b/tests/windows/tray-proxy-deadline.test.ts similarity index 94% rename from tests/tray-proxy-deadline.test.ts rename to tests/windows/tray-proxy-deadline.test.ts index f62f60e648..c08635210d 100644 --- a/tests/tray-proxy-deadline.test.ts +++ b/tests/windows/tray-proxy-deadline.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { discoverStableProxyForRestart } from "../src/cli/tray-proxy"; +import { discoverStableProxyForRestart } from "../../src/cli/tray-proxy"; describe("restart discovery deadline", () => { test("fails closed when the deadline expires after the first absence observation", async () => { diff --git a/tests/tray-proxy.test.ts b/tests/windows/tray-proxy.test.ts similarity index 99% rename from tests/tray-proxy.test.ts rename to tests/windows/tray-proxy.test.ts index e5f0d45167..983e0b456f 100644 --- a/tests/tray-proxy.test.ts +++ b/tests/windows/tray-proxy.test.ts @@ -7,7 +7,7 @@ import { type ProxyRestartIo, type ProxyRestartLive, type TrayProxyStartIo, -} from "../src/cli/tray-proxy"; +} from "../../src/cli/tray-proxy"; function startIo(overrides: Partial = {}) { const calls: string[] = []; diff --git a/tests/win-exec.test.ts b/tests/windows/win-exec.test.ts similarity index 99% rename from tests/win-exec.test.ts rename to tests/windows/win-exec.test.ts index 26375a47fa..7985452951 100644 --- a/tests/win-exec.test.ts +++ b/tests/windows/win-exec.test.ts @@ -5,7 +5,7 @@ import { escapeCmdCommand, resolveWindowsCommand, shellInvocation, -} from "../src/lib/win-exec"; +} from "../../src/lib/win-exec"; describe("escapeCmdArg / escapeCmdCommand (cross-spawn parity)", () => { test("plain token is quoted, with the quotes themselves caret-escaped", () => { diff --git a/tests/win-paths.test.ts b/tests/windows/win-paths.test.ts similarity index 98% rename from tests/win-paths.test.ts rename to tests/windows/win-paths.test.ts index 9a041099e6..8317dd6117 100644 --- a/tests/win-paths.test.ts +++ b/tests/windows/win-paths.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { splitWindowsEnvPrefix, windowsEnvIndirectBatchPathList, windowsEnvIndirectBatchValue } from "../src/lib/win-paths"; +import { splitWindowsEnvPrefix, windowsEnvIndirectBatchPathList, windowsEnvIndirectBatchValue } from "../../src/lib/win-paths"; const escape = (value: string): string => value.replace(/%/g, "%%"); diff --git a/tests/windows-atomic-replace.test.ts b/tests/windows/windows-atomic-replace.test.ts similarity index 96% rename from tests/windows-atomic-replace.test.ts rename to tests/windows/windows-atomic-replace.test.ts index 8e7e6c4712..ce6a10679f 100644 --- a/tests/windows-atomic-replace.test.ts +++ b/tests/windows/windows-atomic-replace.test.ts @@ -13,7 +13,7 @@ */ import { describe, expect, test } from "bun:test"; -import { renameAtomicFile, type AtomicRenameIO } from "../src/lib/windows-atomic-replace"; +import { renameAtomicFile, type AtomicRenameIO } from "../../src/lib/windows-atomic-replace"; /** A rename that fails `failures` times with `code`, then succeeds. */ function io(failures: number, code = "EBUSY", platform: NodeJS.Platform = "win32") { diff --git a/tests/windows-deploy-close-regressions.test.ts b/tests/windows/windows-deploy-close-regressions.test.ts similarity index 98% rename from tests/windows-deploy-close-regressions.test.ts rename to tests/windows/windows-deploy-close-regressions.test.ts index f917b604ed..e3bae4fc0b 100644 --- a/tests/windows-deploy-close-regressions.test.ts +++ b/tests/windows/windows-deploy-close-regressions.test.ts @@ -1,13 +1,14 @@ import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; +import { repoPath } from "../helpers/repo-root"; // Source-contract regressions for the final fixes that let devlog // 260702_windows-deploy-stability close: the ocx.cmd shell-less restart (A), F9 systemd no-DBUS // SSH detection (E), and the F4 explicit-localhost bind symmetry (D). These files run top-level or // platform-gated logic, so guard the invariants at the source level (repo convention — see // ocx-launcher-source.test.ts / service.test.ts). -const read = (rel: string) => readFileSync(join(import.meta.dir, "..", rel), "utf8"); +const read = (rel: string) => readFileSync(repoPath(rel), "utf8"); describe("update-job restart avoids the shell-less .cmd EINVAL (Windows, bun/source)", () => { const src = read("src/update/job.ts"); diff --git a/tests/windows-elevation-spawn.test.ts b/tests/windows/windows-elevation-spawn.test.ts similarity index 99% rename from tests/windows-elevation-spawn.test.ts rename to tests/windows/windows-elevation-spawn.test.ts index 4f731a2b28..3eaaec1258 100644 --- a/tests/windows-elevation-spawn.test.ts +++ b/tests/windows/windows-elevation-spawn.test.ts @@ -19,14 +19,14 @@ import { setTrustedWindowsElevationExecutablesForTests, startElevatedSchtasksCreateAndRun, startPowerShellCommand, -} from "../src/lib/windows-elevation"; +} from "../../src/lib/windows-elevation"; import { evaluateSchedulerInstallRestartReconciliation, finalizeWindowsSchedulerServiceRegistration, schedulerVerificationMaySettle, setFinalizeWindowsSchedulerHooksForTests, -} from "../src/service"; -import type { WindowsSchedulerInstallVerification } from "../src/service"; +} from "../../src/service"; +import type { WindowsSchedulerInstallVerification } from "../../src/service"; /** Linux CI fakes win32 without a real System32; keep elevation paths production-shaped. */ const FAKE_TRUSTED_ELEVATION_EXES = { diff --git a/tests/windows-elevation.test.ts b/tests/windows/windows-elevation.test.ts similarity index 99% rename from tests/windows-elevation.test.ts rename to tests/windows/windows-elevation.test.ts index 2376ebaa25..dc2f207b7b 100644 --- a/tests/windows-elevation.test.ts +++ b/tests/windows/windows-elevation.test.ts @@ -20,7 +20,7 @@ import { setWindowsElevationProbeForTests, toWindowsSchtasksError, windowsCmdQuote, -} from "../src/lib/windows-elevation"; +} from "../../src/lib/windows-elevation"; describe("windows elevation helpers", () => { test("detects English and German access-denied text", () => { diff --git a/tests/windows-popup-fix.test.ts b/tests/windows/windows-popup-fix.test.ts similarity index 96% rename from tests/windows-popup-fix.test.ts rename to tests/windows/windows-popup-fix.test.ts index a480ebcc57..5ef9c99f76 100644 --- a/tests/windows-popup-fix.test.ts +++ b/tests/windows/windows-popup-fix.test.ts @@ -12,14 +12,15 @@ import { afterEach, describe, expect, test } from "bun:test"; import { readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; -import { readProcessStartMsBatch } from "../src/codex/app-server-processes"; +import { readProcessStartMsBatch } from "../../src/codex/app-server-processes"; import { decodeWindowsIdentityPowerShellOutputForTests, resolveEffectiveUserIdentity, windowsIdentityPowerShellCommandForTests, windowsIdentityPowerShellSpawnOptionsForTests, -} from "../src/codex/user-identity"; -import { setTrustedWindowsElevationExecutablesForTests } from "../src/lib/windows-elevation"; +} from "../../src/codex/user-identity"; +import { setTrustedWindowsElevationExecutablesForTests } from "../../src/lib/windows-elevation"; +import { repoPath } from "../helpers/repo-root"; const TRUSTED_POWERSHELL = "C:\\trusted-system32\\WindowsPowerShell\\v1.0\\powershell.exe"; @@ -131,7 +132,7 @@ describe("no direct PowerShell argv carries the Bun-incompatible window flag (#1 // quote style, tolerating whitespace or a line break between them. const FORBIDDEN_ARGV = /["']-WindowStyle["']\s*,\s*["']Hidden["']/; - const srcRoot = join(import.meta.dir, "..", "src"); + const srcRoot = repoPath("src"); test("no src/**/*.ts passes -WindowStyle Hidden as an argv pair", () => { const offenders = runtimeFiles(srcRoot) diff --git a/tests/windows-scheduler-install-verification.test.ts b/tests/windows/windows-scheduler-install-verification.test.ts similarity index 99% rename from tests/windows-scheduler-install-verification.test.ts rename to tests/windows/windows-scheduler-install-verification.test.ts index 29746effd1..cc27196211 100644 --- a/tests/windows-scheduler-install-verification.test.ts +++ b/tests/windows/windows-scheduler-install-verification.test.ts @@ -11,7 +11,7 @@ import { windowsSchedulerCsvIncludesTask, windowsSchedulerTaskInstalled, windowsTaskRegistrationHealthy, -} from "../src/service"; +} from "../../src/service"; const TEST_WINDOWS_TASK_SID = "S-1-5-21-111-222-333-1001"; diff --git a/tests/windows-secret-acl.test.ts b/tests/windows/windows-secret-acl.test.ts similarity index 99% rename from tests/windows-secret-acl.test.ts rename to tests/windows/windows-secret-acl.test.ts index e026d016f8..dddbd38084 100644 --- a/tests/windows-secret-acl.test.ts +++ b/tests/windows/windows-secret-acl.test.ts @@ -31,17 +31,18 @@ import { timedOutSecretPathCountForTests, type HardenResult, type IcaclsResult, -} from "../src/lib/windows-secret-acl"; -import { atomicWriteFile } from "../src/config"; -import { hardenStableLockFile } from "../src/codex/native-main-lock-file"; -import { nativeMainClaimPath, withNativeMainSharedClaim } from "../src/codex/native-main-claim"; -import { NATIVE_MAIN_OWNER_DB, retainNativeMainOwner } from "../src/codex/native-main-owner"; +} from "../../src/lib/windows-secret-acl"; +import { atomicWriteFile } from "../../src/config"; +import { hardenStableLockFile } from "../../src/codex/native-main-lock-file"; +import { nativeMainClaimPath, withNativeMainSharedClaim } from "../../src/codex/native-main-claim"; +import { NATIVE_MAIN_OWNER_DB, retainNativeMainOwner } from "../../src/codex/native-main-owner"; import { resetWindowsPrincipalForTests, resolveCurrentWindowsPrincipal, setAsyncWindowsPrincipalRunnerForTests, setWindowsPrincipalRunnerForTests, -} from "../src/lib/windows-user-principal"; +} from "../../src/lib/windows-user-principal"; +import { repoPath } from "../helpers/repo-root"; let testDir = ""; @@ -589,7 +590,7 @@ describe("non-Windows determinism", () => { describe("icacls executable authority", () => { test("default runners resolve icacls from the trusted System32 path, not PATH", () => { - const src = readFileSync(join(import.meta.dir, "..", "src", "lib", "windows-secret-acl.ts"), "utf8"); + const src = readFileSync(repoPath("src", "lib", "windows-secret-acl.ts"), "utf8"); expect(src).toContain("resolveTrustedWindowsIcaclsExe"); expect(src).not.toMatch(/Bun\.spawn(?:Sync)?\(\["icacls\.exe"/); }); diff --git a/tests/windows-service-mutation-lock.test.ts b/tests/windows/windows-service-mutation-lock.test.ts similarity index 93% rename from tests/windows-service-mutation-lock.test.ts rename to tests/windows/windows-service-mutation-lock.test.ts index 468b8d5527..45dfaf63bc 100644 --- a/tests/windows-service-mutation-lock.test.ts +++ b/tests/windows/windows-service-mutation-lock.test.ts @@ -5,8 +5,9 @@ import { pathToFileURL } from "node:url"; import { WindowsServiceMutationBusyError, withWindowsServiceMutationLock, -} from "../src/lib/windows-service-mutation-lock"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +} from "../../src/lib/windows-service-mutation-lock"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { repoPath, repoRoot } from "../helpers/repo-root"; // The lock path is injected throughout so the suite never opens the real per-user lock and // therefore never serializes against a genuine `ocx service` run on the developer machine. @@ -39,7 +40,7 @@ async function waitForOwnedChild(child: ReturnType): Promise { return Bun.spawn([process.execPath, "-e", source], { - cwd: join(import.meta.dir, ".."), + cwd: repoRoot(), env: { ...process.env }, stdin: "ignore", stdout: "pipe", @@ -70,7 +71,7 @@ afterEach(async () => { test("a second service mutation is refused while another process holds the lock", async () => { const readyPath = join(testRoot, "holder-ready"); const releasePath = join(testRoot, "holder-release"); - const lockModuleUrl = pathToFileURL(join(import.meta.dir, "../src/lib/windows-service-mutation-lock.ts")).href; + const lockModuleUrl = pathToFileURL(repoPath("src/lib/windows-service-mutation-lock.ts")).href; const child = spawnHolder(` import { existsSync, writeFileSync } from "node:fs"; import { withWindowsServiceMutationLock } from ${JSON.stringify(lockModuleUrl)}; @@ -112,7 +113,7 @@ test("a second service mutation is refused while another process holds the lock" test("an abruptly exited holder releases the OS-backed transaction without stale recovery", async () => { const enteredPath = join(testRoot, "crashed-holder-entered"); - const lockModuleUrl = pathToFileURL(join(import.meta.dir, "../src/lib/windows-service-mutation-lock.ts")).href; + const lockModuleUrl = pathToFileURL(repoPath("src/lib/windows-service-mutation-lock.ts")).href; const child = spawnHolder(` import { writeFileSync } from "node:fs"; import { withWindowsServiceMutationLock } from ${JSON.stringify(lockModuleUrl)}; diff --git a/tests/windows-service-wrappers.test.ts b/tests/windows/windows-service-wrappers.test.ts similarity index 96% rename from tests/windows-service-wrappers.test.ts rename to tests/windows/windows-service-wrappers.test.ts index b7c3e7bd67..f9d7c2a3ed 100644 --- a/tests/windows-service-wrappers.test.ts +++ b/tests/windows/windows-service-wrappers.test.ts @@ -19,9 +19,10 @@ import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { windowsWrapperKillScript } from "../src/lib/windows-service-wrappers"; +import { windowsWrapperKillScript } from "../../src/lib/windows-service-wrappers"; +import { repoPath } from "../helpers/repo-root"; -const read = (rel: string) => readFileSync(join(import.meta.dir, "..", rel), "utf8"); +const read = (rel: string) => readFileSync(repoPath(rel), "utf8"); const HOME_A = "C:\\Users\\ocx\\.opencodex"; const HOME_B = "C:\\Users\\ocx\\other-home\\.opencodex"; diff --git a/tests/windows-text-decoding.test.ts b/tests/windows/windows-text-decoding.test.ts similarity index 98% rename from tests/windows-text-decoding.test.ts rename to tests/windows/windows-text-decoding.test.ts index 4262b0a499..d9582a66e7 100644 --- a/tests/windows-text-decoding.test.ts +++ b/tests/windows/windows-text-decoding.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { decodeWindowsTextBytes } from "../src/lib/windows-text"; +import { decodeWindowsTextBytes } from "../../src/lib/windows-text"; describe("Windows system text decoding (#1573)", () => { test("preserves strict UTF-8 before considering a legacy code page", () => { diff --git a/tests/windows-tray-restart-hardening.test.ts b/tests/windows/windows-tray-restart-hardening.test.ts similarity index 93% rename from tests/windows-tray-restart-hardening.test.ts rename to tests/windows/windows-tray-restart-hardening.test.ts index 8e137d9bce..5bf57dc135 100644 --- a/tests/windows-tray-restart-hardening.test.ts +++ b/tests/windows/windows-tray-restart-hardening.test.ts @@ -1,8 +1,9 @@ import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; +import { repoPath } from "../helpers/repo-root"; -const source = readFileSync(join(import.meta.dir, "../src/tray/windows-tray.ps1"), "utf8"); +const source = readFileSync(repoPath("src/tray/windows-tray.ps1"), "utf8"); describe("Windows tray restart process hardening", () => { test("fails a pending action when tracked process state cannot be inspected", () => { diff --git a/tests/windows-tray-run-limit.test.ts b/tests/windows/windows-tray-run-limit.test.ts similarity index 97% rename from tests/windows-tray-run-limit.test.ts rename to tests/windows/windows-tray-run-limit.test.ts index 115d844759..dd14e5a523 100644 --- a/tests/windows-tray-run-limit.test.ts +++ b/tests/windows/windows-tray-run-limit.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { buildWindowsTrayRunCommand, buildWindowsTrayPowerShellCommand, -} from "../src/tray/windows"; +} from "../../src/tray/windows"; describe("windows tray Run registration (#696)", () => { test("short wscript Run command stays within the 260-character Windows limit under long paths", () => { diff --git a/tests/windows-tray.test.ts b/tests/windows/windows-tray.test.ts similarity index 96% rename from tests/windows-tray.test.ts rename to tests/windows/windows-tray.test.ts index 3e17c667c8..4867834db7 100644 --- a/tests/windows-tray.test.ts +++ b/tests/windows/windows-tray.test.ts @@ -26,20 +26,20 @@ import { windowsTrayRegistrationIsStale, windowsRegistryParentShowsRunKey, type WindowsTrayEntry, -} from "../src/tray/windows"; -import { decodeWindowsTextBytes } from "../src/lib/windows-text"; +} from "../../src/tray/windows"; +import { decodeWindowsTextBytes } from "../../src/lib/windows-text"; import { hardenSecretPath, hardenedSecretPathCountForTests, resetHardenedStateForTests, setIcaclsRunnerForTests, setPlatformForTests, -} from "../src/lib/windows-secret-acl"; -import { handleManagementAPI } from "../src/server/management-api"; -import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../src/server/management/system-restart"; -import type { OcxConfig } from "../src/types"; -import { INTERNAL_DEADLINE_MS, SPAWN_BUDGET_MS } from "./helpers/test-budget"; -import { removeTreeWithRetry } from "./helpers/remove-tree"; +} from "../../src/lib/windows-secret-acl"; +import { handleManagementAPI } from "../../src/server/management-api"; +import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../../src/server/management/system-restart"; +import type { OcxConfig } from "../../src/types"; +import { INTERNAL_DEADLINE_MS, SPAWN_BUDGET_MS } from "../helpers/test-budget"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; const entry: WindowsTrayEntry = { bun: "C:\\사용자 공간\\%TEMP% ! ^ ( ) & 검증\\bun.exe", @@ -327,9 +327,9 @@ describe("Windows tray packaging and command safety", () => { }); test("PowerShell controller uses mutex/event shutdown and bans command evaluation", () => { - const typescript = readFileSync(join(import.meta.dir, "..", "src", "tray", "windows.ts"), "utf8"); - const source = readFileSync(join(import.meta.dir, "..", "src", "tray", "windows-tray.ps1"), "utf8"); - const cli = readFileSync(join(import.meta.dir, "..", "src", "cli", "index.ts"), "utf8"); + const typescript = readFileSync(repoPath("src", "tray", "windows.ts"), "utf8"); + const source = readFileSync(repoPath("src", "tray", "windows-tray.ps1"), "utf8"); + const cli = readFileSync(repoPath("src", "cli", "index.ts"), "utf8"); expect(typescript).not.toContain("\u0000"); expect(typescript).toContain("OCX_TRAY_ENTRY_B64"); expect(typescript).not.toContain("$startInfo.UseShellExecute = $true"); @@ -400,7 +400,7 @@ describe("Windows tray packaging and command safety", () => { const directory = mkdtempSync(join(tmpdir(), "ocx-tray-inheritance-")); const pidPath = join(directory, "child.pid"); const childPath = join(directory, "child & %TEMP% 테스트.ts"); - copyFileSync(join(import.meta.dir, "helpers", "windows-tray-inheritance-child.ts"), childPath); + copyFileSync(helperPath("windows-tray-inheritance-child.ts"), childPath); const previousPidPath = process.env.OCX_TRAY_TEST_PID_FILE; const server = Bun.serve({ hostname: "127.0.0.1", @@ -454,7 +454,7 @@ describe("Windows tray packaging and command safety", () => { }, { timeout: TRAY_LAUNCH_TIMEOUT_MS }); test("ships branded multi-size Windows tray icons", () => { - const assets = join(import.meta.dir, "..", "src", "tray", "assets"); + const assets = repoPath("src", "tray", "assets"); for (const name of ["online", "warning", "offline"]) { const path = join(assets, `opencodex-tray-${name}.ico`); expect(existsSync(path)).toBe(true); @@ -487,7 +487,7 @@ describe("Windows tray packaging and command safety", () => { }); test("copies the tray script into the hardened home and gates all update lanes", () => { - const root = join(import.meta.dir, ".."); + const root = repoRoot(); const tray = readFileSync(join(root, "src", "tray", "windows.ts"), "utf8"); expect(tray).toContain('join(getConfigDir(), "opencodex-tray.ps1")'); expect(tray).toContain('join(import.meta.dir, "assets", name)'); @@ -556,4 +556,5 @@ describe("Windows tray packaging and command safety", () => { expect(parseWindowsTrayRunValue(asUtf8, runValue)).not.toBe(command); }); }); -import { ManagementRequest as Request } from "./helpers/management-auth"; +import { ManagementRequest as Request } from "../helpers/management-auth"; +import { helperPath, repoPath, repoRoot } from "../helpers/repo-root"; diff --git a/tests/windows-user-principal-nonascii.test.ts b/tests/windows/windows-user-principal-nonascii.test.ts similarity index 99% rename from tests/windows-user-principal-nonascii.test.ts rename to tests/windows/windows-user-principal-nonascii.test.ts index 1fce85992e..91a8f3da64 100644 --- a/tests/windows-user-principal-nonascii.test.ts +++ b/tests/windows/windows-user-principal-nonascii.test.ts @@ -8,7 +8,7 @@ import { setAsyncWindowsPrincipalRunnerForTests, setWindowsPrincipalLocaleForTests, setWindowsPrincipalRunnerForTests, -} from "../src/lib/windows-user-principal"; +} from "../../src/lib/windows-user-principal"; /** * The identity lookup shells out to `powershell.exe` and reads its stdout. Windows diff --git a/tests/windows-user-principal.test.ts b/tests/windows/windows-user-principal.test.ts similarity index 99% rename from tests/windows-user-principal.test.ts rename to tests/windows/windows-user-principal.test.ts index bb11e307d5..b67bbbafa3 100644 --- a/tests/windows-user-principal.test.ts +++ b/tests/windows/windows-user-principal.test.ts @@ -9,11 +9,11 @@ import { setAsyncWindowsPrincipalRunnerForTests, setWindowsPrincipalRunnerForTests, windowsPrincipalPowerShellCommandForTests, -} from "../src/lib/windows-user-principal"; +} from "../../src/lib/windows-user-principal"; import { setTrustedWindowsElevationExecutablesForTests, WindowsSystemDirectoryFfiUnavailableError, -} from "../src/lib/windows-elevation"; +} from "../../src/lib/windows-elevation"; const ok = (stdout = "S-1-5-21-111-222-333-1001\r\nEXAMPLE\\Owner\r\n") => ({ success: true, diff --git a/tests/winsw-stop-hardening.test.ts b/tests/windows/winsw-stop-hardening.test.ts similarity index 87% rename from tests/winsw-stop-hardening.test.ts rename to tests/windows/winsw-stop-hardening.test.ts index e3e351eff9..4b8dfb6b39 100644 --- a/tests/winsw-stop-hardening.test.ts +++ b/tests/windows/winsw-stop-hardening.test.ts @@ -1,10 +1,11 @@ import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; +import { repoPath } from "../helpers/repo-root"; describe("WinSW lifecycle stop hardening", () => { test("re-verifies native service state after stopwait before returning", () => { - const source = readFileSync(join(import.meta.dir, "../src/lib/winsw.ts"), "utf8"); + const source = readFileSync(repoPath("src/lib/winsw.ts"), "utf8"); const start = source.indexOf("export function stopWinswService"); const end = source.indexOf("export function uninstallWinswService", start); const stop = source.slice(start, end);