diff --git a/packages/daemon-contracts/package.json b/packages/daemon-contracts/package.json index aef1c885c6..bc4d0cf0af 100644 --- a/packages/daemon-contracts/package.json +++ b/packages/daemon-contracts/package.json @@ -32,10 +32,6 @@ "types": "./src/daemon-registration.ts", "default": "./src/daemon-registration.ts" }, - "./daemon-registration-fixtures": { - "types": "./src/daemon-registration.fixtures.ts", - "default": "./src/daemon-registration.fixtures.ts" - }, "./daemon-registration-owner": { "types": "./src/daemon-registration-owner.ts", "default": "./src/daemon-registration-owner.ts" @@ -60,10 +56,6 @@ "types": "./src/device-claims.ts", "default": "./src/device-claims.ts" }, - "./device-claims-fixtures": { - "types": "./src/device-claims.fixtures.ts", - "default": "./src/device-claims.fixtures.ts" - }, "./provider-credentials": { "types": "./src/provider-credentials.ts", "default": "./src/provider-credentials.ts" @@ -80,5 +72,8 @@ "types": "./src/session-repair-tombstone.ts", "default": "./src/session-repair-tombstone.ts" } + }, + "devDependencies": { + "@agent-device/test-kit": "workspace:*" } } diff --git a/packages/daemon-contracts/src/daemon-diagnostics-scope.test.ts b/packages/daemon-contracts/src/daemon-diagnostics-scope.test.ts index 0ee86269d4..2f462b5c3e 100644 --- a/packages/daemon-contracts/src/daemon-diagnostics-scope.test.ts +++ b/packages/daemon-contracts/src/daemon-diagnostics-scope.test.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import { afterEach, beforeEach, test } from 'vitest'; import { emitDiagnostic } from '@agent-device/host-kit/diagnostics'; import { withDaemonDiagnosticsScope } from './daemon-diagnostics-scope.ts'; -import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; +import { mkdtempForTestSync } from '@agent-device/test-kit'; let dir: string; let logPath: string; diff --git a/packages/daemon-contracts/src/daemon-policy-file.test.ts b/packages/daemon-contracts/src/daemon-policy-file.test.ts index 3ebae4206b..2757f43405 100644 --- a/packages/daemon-contracts/src/daemon-policy-file.test.ts +++ b/packages/daemon-contracts/src/daemon-policy-file.test.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import { expect, test } from 'vitest'; import { commandDescriptors } from '@agent-device/command-registry/registry'; import type { CommandDescriptor } from '@agent-device/command-registry/types'; -import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; +import { mkdtempForTestSync } from '@agent-device/test-kit'; import { DAEMON_POLICY_ENV, loadDaemonPolicy, parseDaemonPolicy } from './daemon-policy-file.ts'; const SOURCE = '/etc/agent-device/policy.json'; diff --git a/packages/daemon-contracts/src/daemon-registration.test.ts b/packages/daemon-contracts/src/daemon-registration.test.ts index 40cf1e3100..f4ba3894d0 100644 --- a/packages/daemon-contracts/src/daemon-registration.test.ts +++ b/packages/daemon-contracts/src/daemon-registration.test.ts @@ -7,8 +7,7 @@ import { readRegisteredDaemonIdentity, readRegisteredDaemonOwnership, } from './daemon-registration.ts'; -import { publishDaemonRegistration } from './daemon-registration.fixtures.ts'; -import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; +import { mkdtempForTestSync, publishDaemonRegistration } from '@agent-device/test-kit'; const roots: string[] = []; diff --git a/packages/daemon-contracts/src/daemon-shutdown-report.test.ts b/packages/daemon-contracts/src/daemon-shutdown-report.test.ts index 6aa1bec8d9..ef4a31157c 100644 --- a/packages/daemon-contracts/src/daemon-shutdown-report.test.ts +++ b/packages/daemon-contracts/src/daemon-shutdown-report.test.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import { expect, test } from 'vitest'; import type { DeviceLease } from '@agent-device/contracts/device'; import { readDaemonShutdownReport, buildDaemonShutdownReport } from './daemon-shutdown-report.ts'; -import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; +import { mkdtempForTestSync } from '@agent-device/test-kit'; const claim = { deviceKey: 'local:android:none:emulator-5554', diff --git a/packages/daemon-contracts/src/daemon-stop.test.ts b/packages/daemon-contracts/src/daemon-stop.test.ts index 0c886a8fd3..e5003cb030 100644 --- a/packages/daemon-contracts/src/daemon-stop.test.ts +++ b/packages/daemon-contracts/src/daemon-stop.test.ts @@ -1,6 +1,6 @@ import fs from 'node:fs'; import { afterEach, expect, test, vi } from 'vitest'; -import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; +import { mkdtempForTestSync } from '@agent-device/test-kit'; const mocks = vi.hoisted(() => ({ stopAndRetireDaemon: vi.fn() })); vi.mock('./daemon-registration-owner.ts', () => ({ diff --git a/packages/daemon-contracts/src/device-claim-paths.test.ts b/packages/daemon-contracts/src/device-claim-paths.test.ts new file mode 100644 index 0000000000..5143bbd8b8 --- /dev/null +++ b/packages/daemon-contracts/src/device-claim-paths.test.ts @@ -0,0 +1,21 @@ +import path from 'node:path'; +import { expect, test, vi } from 'vitest'; +import { deviceClaimFilePath } from '@agent-device/test-kit'; +import { resolveDeviceClaimPath } from './device-claim-paths.ts'; + +const DEVICE_KEYS = ['local:android:none:emulator-5554', 'local:ios:ios:ABCD-1234']; + +test('the claim file path test-kit restates matches the production resolver', () => { + vi.stubEnv('AGENT_DEVICE_CLAIMS_DIR', ''); + for (const key of DEVICE_KEYS) { + expect(deviceClaimFilePath(key)).toBe(resolveDeviceClaimPath(key)); + } +}); + +test('the restated path follows the claims directory override like the resolver', () => { + vi.stubEnv('AGENT_DEVICE_CLAIMS_DIR', path.join('relative', 'claims')); + for (const key of DEVICE_KEYS) { + expect(deviceClaimFilePath(key)).toBe(resolveDeviceClaimPath(key)); + expect(path.isAbsolute(deviceClaimFilePath(key))).toBe(true); + } +}); diff --git a/packages/daemon/src/sessions/device/__tests__/device-claim-reconciliation.test.ts b/packages/daemon-contracts/src/device-claim-reconciliation.test.ts similarity index 96% rename from packages/daemon/src/sessions/device/__tests__/device-claim-reconciliation.test.ts rename to packages/daemon-contracts/src/device-claim-reconciliation.test.ts index 4d51b8a280..ebbe776150 100644 --- a/packages/daemon/src/sessions/device/__tests__/device-claim-reconciliation.test.ts +++ b/packages/daemon-contracts/src/device-claim-reconciliation.test.ts @@ -1,4 +1,4 @@ -import { createDeviceClaimReconciler } from '@agent-device/daemon-contracts/device-claims-fixtures'; +import { createDeviceClaimReconciler } from './device-claim-reconciliation.ts'; import { appLogResourceStore } from '@agent-device/capture-kit/app-log-durable-resource'; import fs from 'node:fs'; import path from 'node:path'; @@ -15,7 +15,7 @@ import { mkdtempForTestSync, unavailableDeploymentSnapshotAndShutdownOperationFacts, } from '@agent-device/test-kit'; -import type { DeviceClaim } from '@agent-device/daemon-contracts/device-claims'; +import type { DeviceClaim } from './device-claims.ts'; const scope = { signal: new AbortController().signal, diff --git a/packages/daemon-contracts/src/device-claims.fixtures.ts b/packages/daemon-contracts/src/device-claims.fixtures.ts deleted file mode 100644 index 8fbf1d6f35..0000000000 --- a/packages/daemon-contracts/src/device-claims.fixtures.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { resolveDeviceClaimPath } from './device-claim-paths.ts'; -export { createDeviceClaimReconciler } from './device-claim-reconciliation.ts'; diff --git a/packages/daemon-contracts/src/session-artifact-paths.test.ts b/packages/daemon-contracts/src/session-artifact-paths.test.ts index f5831fe17d..f960c0fce4 100644 --- a/packages/daemon-contracts/src/session-artifact-paths.test.ts +++ b/packages/daemon-contracts/src/session-artifact-paths.test.ts @@ -4,7 +4,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; import { requireManagedSessionArtifactPath, resolveSessionDir } from './session-artifact-paths.ts'; -import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; +import { mkdtempForTestSync } from '@agent-device/test-kit'; test('resolveSessionDir keeps every session dir beneath the sessions dir', () => { const sessionsDir = path.join( diff --git a/packages/daemon-contracts/src/session-repair-tombstone.test.ts b/packages/daemon-contracts/src/session-repair-tombstone.test.ts index aaf252c0bb..33db7e6a99 100644 --- a/packages/daemon-contracts/src/session-repair-tombstone.test.ts +++ b/packages/daemon-contracts/src/session-repair-tombstone.test.ts @@ -3,7 +3,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { expect, test } from 'vitest'; import { AppError } from '@agent-device/kernel/errors'; -import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; +import { mkdtempForTestSync } from '@agent-device/test-kit'; import { findUnrecoveredRepairCommitFailure, readRepairTombstoneFile, diff --git a/packages/daemon-contracts/src/tmp-dir.fixtures.ts b/packages/daemon-contracts/src/tmp-dir.fixtures.ts deleted file mode 100644 index 6df3c9f35c..0000000000 --- a/packages/daemon-contracts/src/tmp-dir.fixtures.ts +++ /dev/null @@ -1,12 +0,0 @@ -import fs from 'node:fs'; -import fsPromises from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; - -export async function mkdtempForTest(prefix: string): Promise { - return fsPromises.mkdtemp(path.join(os.tmpdir(), prefix)); -} - -export function mkdtempForTestSync(prefix: string): string { - return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); -} diff --git a/packages/daemon-contracts/tsconfig.json b/packages/daemon-contracts/tsconfig.json index ab0246c7e8..f9223e85e5 100644 --- a/packages/daemon-contracts/tsconfig.json +++ b/packages/daemon-contracts/tsconfig.json @@ -15,6 +15,7 @@ { "path": "../command-registry" }, { "path": "../contracts" }, { "path": "../host-kit" }, - { "path": "../kernel" } + { "path": "../kernel" }, + { "path": "../test-kit" } ] } diff --git a/packages/daemon/src/resources/device/__tests__/device-claim-admission.test.ts b/packages/daemon/src/resources/device/__tests__/device-claim-admission.test.ts index d533809a9d..57830bb24d 100644 --- a/packages/daemon/src/resources/device/__tests__/device-claim-admission.test.ts +++ b/packages/daemon/src/resources/device/__tests__/device-claim-admission.test.ts @@ -1,4 +1,4 @@ -import { resolveDeviceClaimPath } from '@agent-device/daemon-contracts/device-claims-fixtures'; +import { ANDROID_EMULATOR, deviceClaimFilePath } from '@agent-device/test-kit'; import { expect, test } from 'vitest'; import { daemonPlatformServicesFixture, @@ -8,7 +8,6 @@ import fs from 'node:fs'; import path from 'node:path'; import { localRuntimeOwner, providerRuntimeOwner } from '@agent-device/contracts/platform-runtime'; import { asAppError } from '@agent-device/kernel/errors'; -import { ANDROID_EMULATOR } from '@agent-device/test-kit'; import { isolatedDeviceClaimStores, retainOrphanedDeviceClaims, @@ -168,7 +167,7 @@ test('an ordinary owner is refused for an unknown-schema claim record, not waved const { stateDir, claimsDir } = setup(); fs.mkdirSync(claimsDir, { recursive: true }); const deviceKey = canonicalLocalDeviceKey(ANDROID_EMULATOR); - const claimPath = resolveDeviceClaimPath(deviceKey); + const claimPath = deviceClaimFilePath(deviceKey); const record = JSON.stringify({ schemaVersion: 3, deviceKey, diff --git a/packages/daemon/src/resources/device/__tests__/device-claim-prune.test.ts b/packages/daemon/src/resources/device/__tests__/device-claim-prune.test.ts index 3b7bbe3ed0..00cc022268 100644 --- a/packages/daemon/src/resources/device/__tests__/device-claim-prune.test.ts +++ b/packages/daemon/src/resources/device/__tests__/device-claim-prune.test.ts @@ -1,4 +1,9 @@ -import { resolveDeviceClaimPath } from '@agent-device/daemon-contracts/device-claims-fixtures'; +import { + deviceClaimFilePath, + mkdtempForTestSync, + pinOwnProcessStartTime, + publishDaemonRegistration, +} from '@agent-device/test-kit'; import assert from 'node:assert/strict'; import fs from 'node:fs'; @@ -8,9 +13,6 @@ import { reconcileOrphanedDeviceClaims } from '@agent-device/daemon-contracts/de import { acquireProcessLock } from '@agent-device/host-kit/file'; import * as hostProcess from '@agent-device/host-kit/process'; import { readCurrentOwnerIdentity } from '@agent-device/host-kit/process'; -import { publishDaemonRegistration } from '@agent-device/daemon-contracts/daemon-registration-fixtures'; - -import { mkdtempForTestSync, pinOwnProcessStartTime } from '@agent-device/test-kit'; pinOwnProcessStartTime(hostProcess); @@ -29,7 +31,7 @@ function writeClaim( ): void { const deviceKey = `local:android:none:${fileName}`; fs.writeFileSync( - resolveDeviceClaimPath(deviceKey), + deviceClaimFilePath(deviceKey), JSON.stringify({ schemaVersion: 1, deviceKey, @@ -67,7 +69,7 @@ test('reconciles claims whose owner is gone and keeps every other claim', async process.cwd(), ); - const claimPathFor = (name: string) => resolveDeviceClaimPath(`local:android:none:${name}`); + const claimPathFor = (name: string) => deviceClaimFilePath(`local:android:none:${name}`); assert.deepEqual(summary, { examined: 1, reconciled: 1, retained: 0, changed: 0 }); assert.equal(fs.existsSync(claimPathFor('dead.json')), false); assert.equal(fs.existsSync(claimPathFor('live.json')), true); @@ -103,7 +105,7 @@ test('leaves a live successor written while the reconciliation waited for the cl process.env.AGENT_DEVICE_CLAIMS_DIR = claimsDir; const owner = readCurrentOwnerIdentity(); const deviceKey = 'local:android:none:contested'; - const claimPath = resolveDeviceClaimPath(deviceKey); + const claimPath = deviceClaimFilePath(deviceKey); const writeContested = (ownerPid: number, ownerStartTime: string | null, token: string) => { fs.writeFileSync( claimPath, @@ -175,10 +177,7 @@ test('reconciles a claim whose owning daemon was replaced while still running', ); assert.deepEqual(summary, { examined: 1, reconciled: 1, retained: 0, changed: 0 }); - assert.equal( - fs.existsSync(resolveDeviceClaimPath('local:android:none:superseded.json')), - false, - ); + assert.equal(fs.existsSync(deviceClaimFilePath('local:android:none:superseded.json')), false); } finally { fs.rmSync(claimsDir, { recursive: true, force: true }); } @@ -206,7 +205,7 @@ test('leaves a superseded owner in another state dir to the device that asks for assert.deepEqual(summary, { examined: 0, reconciled: 0, retained: 0, changed: 0 }); assert.equal(reconcile.mock.calls.length, 0); - assert.equal(fs.existsSync(resolveDeviceClaimPath('local:android:none:foreign.json')), true); + assert.equal(fs.existsSync(deviceClaimFilePath('local:android:none:foreign.json')), true); } finally { fs.rmSync(claimsDir, { recursive: true, force: true }); } diff --git a/packages/daemon/src/resources/device/__tests__/device-claim-settlement.test.ts b/packages/daemon/src/resources/device/__tests__/device-claim-settlement.test.ts index 72886b8204..e43e2bebc6 100644 --- a/packages/daemon/src/resources/device/__tests__/device-claim-settlement.test.ts +++ b/packages/daemon/src/resources/device/__tests__/device-claim-settlement.test.ts @@ -11,8 +11,11 @@ import { } from '@agent-device/daemon-contracts/device-claims'; import type { DeviceBootObservationService } from '@agent-device/contracts/device-boot'; import type { DeviceInfo } from '@agent-device/kernel/device'; -import { mkdtempForTestSync, pinOwnProcessStartTime } from '@agent-device/test-kit'; -import { publishDaemonRegistration } from '@agent-device/daemon-contracts/daemon-registration-fixtures'; +import { + mkdtempForTestSync, + pinOwnProcessStartTime, + publishDaemonRegistration, +} from '@agent-device/test-kit'; pinOwnProcessStartTime(hostProcess); const device: DeviceInfo = { diff --git a/packages/daemon/src/resources/device/__tests__/device-claims.test.ts b/packages/daemon/src/resources/device/__tests__/device-claims.test.ts index b1c6155d87..74340b5561 100644 --- a/packages/daemon/src/resources/device/__tests__/device-claims.test.ts +++ b/packages/daemon/src/resources/device/__tests__/device-claims.test.ts @@ -13,8 +13,11 @@ import { inspectDeviceClaims, } from '@agent-device/daemon-contracts/device-claims'; import type { DeviceInfo } from '@agent-device/kernel/device'; -import { mkdtempForTestSync, pinOwnProcessStartTime } from '@agent-device/test-kit'; -import { publishDaemonRegistration } from '@agent-device/daemon-contracts/daemon-registration-fixtures'; +import { + mkdtempForTestSync, + pinOwnProcessStartTime, + publishDaemonRegistration, +} from '@agent-device/test-kit'; import * as hostProcess from '@agent-device/host-kit/process'; import { readCurrentOwnerIdentity } from '@agent-device/host-kit/process'; import { acquireProcessLock } from '@agent-device/host-kit/file'; diff --git a/packages/daemon/src/server/__tests__/request-execution-scope-lease-session-end.test.ts b/packages/daemon/src/server/__tests__/request-execution-scope-lease-session-end.test.ts index ded5a2c0f7..8ee9c26b27 100644 --- a/packages/daemon/src/server/__tests__/request-execution-scope-lease-session-end.test.ts +++ b/packages/daemon/src/server/__tests__/request-execution-scope-lease-session-end.test.ts @@ -1,8 +1,7 @@ -import { resolveDeviceClaimPath } from '@agent-device/daemon-contracts/device-claims-fixtures'; +import { deviceClaimFilePath, mkdtempForTestSync } from '@agent-device/test-kit'; import fs from 'node:fs'; import path from 'node:path'; import { expect, test, vi } from 'vitest'; -import { mkdtempForTestSync } from '@agent-device/test-kit'; import { authoringPublication, makeIosSession } from '../../__tests__/support/session-factories.ts'; import { makeSessionStore } from '../../__tests__/support/store-factory.ts'; import { @@ -41,7 +40,7 @@ async function publishLeasedSession( }); if (acquired.status !== 'acquired') throw new Error('expected an acquired claim'); sessionStore.publish(name, { ...session, deviceClaim: acquired.ownership }); - return resolveDeviceClaimPath(acquired.ownership.deviceKey); + return deviceClaimFilePath(acquired.ownership.deviceKey); } function holdSessionLocks( diff --git a/packages/daemon/src/server/__tests__/session-idle-expiry-harness.ts b/packages/daemon/src/server/__tests__/session-idle-expiry-harness.ts index af080d4eb8..705989cb4c 100644 --- a/packages/daemon/src/server/__tests__/session-idle-expiry-harness.ts +++ b/packages/daemon/src/server/__tests__/session-idle-expiry-harness.ts @@ -1,4 +1,4 @@ -import { resolveDeviceClaimPath } from '@agent-device/daemon-contracts/device-claims-fixtures'; +import { deviceClaimFilePath, IOS_SIMULATOR, mkdtempForTestSync } from '@agent-device/test-kit'; import { assert } from 'vitest'; import fs from 'node:fs'; import path from 'node:path'; @@ -15,7 +15,6 @@ import { acquireDeviceClaim, type DeviceClaimSessionOwnership, } from '@agent-device/daemon-contracts/device-claims'; -import { IOS_SIMULATOR, mkdtempForTestSync } from '@agent-device/test-kit'; /** The inactivity window every harness session is already past, except where a test says otherwise. */ export const WINDOW_MS = 1_000; @@ -135,7 +134,7 @@ export function createIdleExpiryHarness(): Readonly<{ fixture.sessionStore.publish(name, session); return { session, deviceClaim: acquired.ownership }; }, - claimFileHeld: (deviceClaim) => fs.existsSync(resolveDeviceClaimPath(deviceClaim.deviceKey)), + claimFileHeld: (deviceClaim) => fs.existsSync(deviceClaimFilePath(deviceClaim.deviceKey)), runUntilIdle: (controller, ms) => { controller.noteSessionsChanged(); return new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/packages/daemon/src/server/http/daemon-metadata-loss.test.ts b/packages/daemon/src/server/http/daemon-metadata-loss.test.ts index e5d4c6368a..2075ae8dde 100644 --- a/packages/daemon/src/server/http/daemon-metadata-loss.test.ts +++ b/packages/daemon/src/server/http/daemon-metadata-loss.test.ts @@ -2,8 +2,7 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { describe, test, vi } from 'vitest'; -import { mkdtempForTestSync } from '@agent-device/test-kit'; -import { publishDaemonRegistration } from '@agent-device/daemon-contracts/daemon-registration-fixtures'; +import { mkdtempForTestSync, publishDaemonRegistration } from '@agent-device/test-kit'; import { readDaemonMetadataLoss, watchDaemonMetadataLoss, diff --git a/packages/daemon/src/server/http/daemon-runtime-lifecycle-shutdown.test.ts b/packages/daemon/src/server/http/daemon-runtime-lifecycle-shutdown.test.ts index 81c91e244e..b618bf8181 100644 --- a/packages/daemon/src/server/http/daemon-runtime-lifecycle-shutdown.test.ts +++ b/packages/daemon/src/server/http/daemon-runtime-lifecycle-shutdown.test.ts @@ -1,8 +1,7 @@ -import { resolveDeviceClaimPath } from '@agent-device/daemon-contracts/device-claims-fixtures'; +import { ANDROID_EMULATOR, deviceClaimFilePath, mkdtempForTestSync } from '@agent-device/test-kit'; import fs from 'node:fs'; import path from 'node:path'; import { afterEach, expect, test, vi } from 'vitest'; -import { ANDROID_EMULATOR, mkdtempForTestSync } from '@agent-device/test-kit'; import { platformResourceCleanupFixture } from '../../__tests__/support/platform-resource-cleanup-fixture.ts'; const lifecycleEvents = vi.hoisted(() => [] as string[]); @@ -292,7 +291,7 @@ test('shutdown includes drain publications, releases their claims and refuses po expect(refusal).toMatchObject({ details: { reason: 'daemon_shutting_down' } }); expect(store.lookup('draining')).toBeUndefined(); expect(store.lookup('late')).toBeUndefined(); - expect(fs.existsSync(resolveDeviceClaimPath(acquired.ownership.deviceKey))).toBe(false); + expect(fs.existsSync(deviceClaimFilePath(acquired.ownership.deviceKey))).toBe(false); } finally { await runtime?.shutdown(); } @@ -410,7 +409,7 @@ test('shutdown joins dispatch completion after force-closing the client before t expect(publicationError).toBeUndefined(); expect(shutdownProbe.finalize).toHaveBeenCalledOnce(); expect(store.lookup('dispatching')).toBeUndefined(); - expect(fs.existsSync(resolveDeviceClaimPath(acquired.ownership.deviceKey))).toBe(false); + expect(fs.existsSync(deviceClaimFilePath(acquired.ownership.deviceKey))).toBe(false); } finally { release(); await request; diff --git a/packages/daemon/src/server/http/daemon-runtime-metadata-ownership.test.ts b/packages/daemon/src/server/http/daemon-runtime-metadata-ownership.test.ts index b2314bf28d..0e897bb4af 100644 --- a/packages/daemon/src/server/http/daemon-runtime-metadata-ownership.test.ts +++ b/packages/daemon/src/server/http/daemon-runtime-metadata-ownership.test.ts @@ -1,8 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { afterEach, expect, test, vi } from 'vitest'; -import { mkdtempForTestSync } from '@agent-device/test-kit'; -import { publishDaemonRegistration } from '@agent-device/daemon-contracts/daemon-registration-fixtures'; +import { mkdtempForTestSync, publishDaemonRegistration } from '@agent-device/test-kit'; const lifecycleEvents = vi.hoisted(() => [] as string[]); const startupFailure = vi.hoisted(() => ({ active: false })); diff --git a/packages/daemon/src/server/http/daemon-session-idle-expiry.test.ts b/packages/daemon/src/server/http/daemon-session-idle-expiry.test.ts index 55d46c5f59..17fc12a43e 100644 --- a/packages/daemon/src/server/http/daemon-session-idle-expiry.test.ts +++ b/packages/daemon/src/server/http/daemon-session-idle-expiry.test.ts @@ -1,4 +1,4 @@ -import { resolveDeviceClaimPath } from '@agent-device/daemon-contracts/device-claims-fixtures'; +import { deviceClaimFilePath } from '@agent-device/test-kit'; import { test } from 'vitest'; import assert from 'node:assert/strict'; import fs from 'node:fs'; @@ -77,7 +77,7 @@ test('a settle that cannot confirm the claim gone keeps the session record stand // A claim store this process cannot write into: the clear reaches NO verdict, which is different // information from a successor having taken the device. - const claimsDir = path.dirname(resolveDeviceClaimPath(deviceClaim.deviceKey)); + const claimsDir = path.dirname(deviceClaimFilePath(deviceClaim.deviceKey)); fs.chmodSync(claimsDir, 0o500); try { const sweeps = createSweepBarrier(); @@ -117,7 +117,7 @@ test('a claim that yields no attributable owner keeps the session record standin // The record this daemon wrote is now unreadable. The clear therefore learns nothing about whether // its claim is gone, which is a different answer from a successor owning the device — and only that // successor case may justify forgetting a session whose daemon is still alive. - fs.writeFileSync(resolveDeviceClaimPath(deviceClaim.deviceKey), '{bad json'); + fs.writeFileSync(deviceClaimFilePath(deviceClaim.deviceKey), '{bad json'); const sweeps = createSweepBarrier(); const controller = createSessionIdleExpiry({ @@ -295,7 +295,7 @@ test('a held-back settle leaves a repair transaction uncommitted, so a later pas sessionStore.retire(sessionStore.lookup('default')!); sessionStore.publish('default', session); - const claimsDir = path.dirname(resolveDeviceClaimPath(deviceClaim.deviceKey)); + const claimsDir = path.dirname(deviceClaimFilePath(deviceClaim.deviceKey)); const sweeps = createSweepBarrier(); const settleParams = { sessionStore, diff --git a/packages/daemon/src/server/http/daemon-shutdown-claims.test.ts b/packages/daemon/src/server/http/daemon-shutdown-claims.test.ts index de1600f3d7..7f3cd9e54f 100644 --- a/packages/daemon/src/server/http/daemon-shutdown-claims.test.ts +++ b/packages/daemon/src/server/http/daemon-shutdown-claims.test.ts @@ -1,6 +1,5 @@ -import { resolveDeviceClaimPath } from '@agent-device/daemon-contracts/device-claims-fixtures'; +import { ANDROID_EMULATOR, deviceClaimFilePath } from '@agent-device/test-kit'; import { afterEach, expect, test, vi } from 'vitest'; -import { ANDROID_EMULATOR } from '@agent-device/test-kit'; import { isolatedDeviceClaimStores, retainOrphanedDeviceClaims, @@ -83,7 +82,7 @@ test('a claim replaced by a successor owner is reported superseded, never releas // from under it, and another owner claims the same device before this daemon // reaches teardown. `clearDeviceClaim` finds a claim it does not own and // deliberately leaves it alone, so "the call resolved" cannot mean "released". - fs.rmSync(resolveDeviceClaimPath(deviceKey)); + fs.rmSync(deviceClaimFilePath(deviceKey)); const successor = await acquireDeviceClaim({ device: ANDROID_EMULATOR, session: 'successor', @@ -112,7 +111,7 @@ test('an undecodable record is reported unattributable, never orphaned or supers // `superseded`; and it is not `orphaned` either, because that bucket's remedy is // `device release --stale`, which proves staleness from the recorded owner this record does not // have. Claiming the bucket would send the operator to a command that refuses this record. - fs.writeFileSync(resolveDeviceClaimPath(deviceKey), '{bad json'); + fs.writeFileSync(deviceClaimFilePath(deviceKey), '{bad json'); const ledger = createDaemonShutdownClaimLedger(); await ledger.releaseClaim(session); diff --git a/packages/daemon-contracts/src/daemon-registration.fixtures.ts b/packages/test-kit/src/daemon-registration-fixtures.ts similarity index 100% rename from packages/daemon-contracts/src/daemon-registration.fixtures.ts rename to packages/test-kit/src/daemon-registration-fixtures.ts diff --git a/packages/test-kit/src/device-claim-fixtures.ts b/packages/test-kit/src/device-claim-fixtures.ts new file mode 100644 index 0000000000..7922437f28 --- /dev/null +++ b/packages/test-kit/src/device-claim-fixtures.ts @@ -0,0 +1,16 @@ +import crypto from 'node:crypto'; +import os from 'node:os'; +import path from 'node:path'; + +/** + * Where a device claim is stored on disk: `/.json`, with the claims + * dir overridable through `AGENT_DEVICE_CLAIMS_DIR`. Restated here so tests pin the layout the CLI + * and the daemon share instead of re-importing the production resolver. + */ +export function deviceClaimFilePath(deviceKey: string): string { + const override = process.env.AGENT_DEVICE_CLAIMS_DIR?.trim(); + const root = override + ? path.resolve(override) + : path.join(os.homedir(), '.agent-device', 'device-claims'); + return path.join(root, `${crypto.createHash('sha256').update(deviceKey).digest('hex')}.json`); +} diff --git a/packages/test-kit/src/index.ts b/packages/test-kit/src/index.ts index 4e18cb8069..bcffe42ac6 100644 --- a/packages/test-kit/src/index.ts +++ b/packages/test-kit/src/index.ts @@ -15,6 +15,8 @@ export { type BoundaryInvariant, } from './boundary-fault-matrix.ts'; export { withNoColor, withNoColorAsync } from './color.ts'; +export { publishDaemonRegistration } from './daemon-registration-fixtures.ts'; +export { deviceClaimFilePath } from './device-claim-fixtures.ts'; export { ANDROID_DEVICE, ANDROID_EMULATOR, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1963fa7ebe..086dfd0760 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -377,6 +377,10 @@ importers: '@agent-device/kernel': specifier: workspace:* version: link:../kernel + devDependencies: + '@agent-device/test-kit': + specifier: workspace:* + version: link:../test-kit packages/device-selection: dependencies: diff --git a/src/__tests__/cli-device-status.test.ts b/src/__tests__/cli-device-status.test.ts index bcb8e37dab..a17b936721 100644 --- a/src/__tests__/cli-device-status.test.ts +++ b/src/__tests__/cli-device-status.test.ts @@ -5,8 +5,11 @@ import { test } from 'vitest'; import * as hostProcess from '@agent-device/host-kit/process'; import { readCurrentOwnerIdentity } from '@agent-device/host-kit/process'; import { runCliCapture } from './cli-capture.ts'; -import { publishDaemonRegistration } from '@agent-device/daemon-contracts/daemon-registration-fixtures'; -import { mkdtempForTestSync, pinOwnProcessStartTime } from '@agent-device/test-kit'; +import { + mkdtempForTestSync, + pinOwnProcessStartTime, + publishDaemonRegistration, +} from '@agent-device/test-kit'; pinOwnProcessStartTime(hostProcess);