import assert from "node:assert/strict"; import { startManagerServer } from "../../mgr/server.js"; import { MemoryAgentRunStore } from "../../mgr/store.js"; import { ManagerClient } from "../../mgr/client.js"; import { buildSessionPvcSpec, createSessionPvc, deleteSessionPvc, getSessionPvcSummary, refreshSessionPvcSummary, runSessionStorageGc, sessionPvcLabelValueFor, sessionPvcNameFor, sanitizeSessionIdForPvc } from "../../mgr/session-pvc.js"; import type { KubectlHandler, SessionPvcOptions } from "../../mgr/session-pvc.js"; import type { SelfTestCase } from "../harness.js"; function makeFakeKubectl(): KubectlHandler { return ({ args, stdin }) => { if (args[0] === "create") { const manifest = stdin ? JSON.parse(stdin) : {}; return { stdout: JSON.stringify({ apiVersion: "v1", kind: "PersistentVolumeClaim", metadata: { name: manifest.metadata?.name, namespace: manifest.metadata?.namespace }, status: { phase: "Bound" } }), stderr: "", exitCode: 0 }; } if (args[0] === "get") { return { stdout: JSON.stringify({ apiVersion: "v1", kind: "PersistentVolumeClaim", metadata: { name: args[2] }, status: { phase: "Bound", capacity: { storage: "1Gi" } } }), stderr: "", exitCode: 0 }; } if (args[0] === "delete") { return { stdout: "", stderr: "", exitCode: 0 }; } return { stdout: "{}", stderr: "", exitCode: 0 }; }; } const selfTest: SelfTestCase = async () => { const fakeKubectl = makeFakeKubectl(); const opts: SessionPvcOptions = { kubectlHandler: fakeKubectl, defaultCodexRolloutSubdir: "sessions" }; const store = new MemoryAgentRunStore(); const sessionId = "sess_test_pvc_001"; store.upsertSession({ sessionId, tenantId: "hwlab", projectId: "pikasTech/HWLAB", backendProfile: "codex", conversationId: null, threadId: null, metadata: {}, expiresAt: new Date(Date.now() + 86_400_000).toISOString(), codexRolloutSubdir: "sessions" }); const summary = await createSessionPvc({ store, sessionId, options: opts }); assert.equal(summary.pvcName, sessionPvcNameFor(sessionId)); assert.equal(summary.pvcPhase, "Bound"); assert.equal(summary.codexRolloutSubdir, "sessions"); assert.equal(sessionPvcNameFor("sess_with_underscores_001"), "agentrun-v01-session-sess-with-underscores-001"); assert.equal(sessionPvcNameFor("Sess.UPPER.001"), "agentrun-v01-session-sess-upper-001"); assert.equal(sessionPvcNameFor("---"), "agentrun-v01-session-default"); const longSessionId = "ses_agentrun_dsflash_go_0606efb7_60fd_4f38_9532_398b46e54808-reset-trcmqlrsnc2r"; const longSessionLabel = sessionPvcLabelValueFor(longSessionId); assert.ok(longSessionLabel.length <= 63); assert.notEqual(longSessionLabel, longSessionId); assert.match(longSessionLabel, /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u); const previousRuntimeNamespace = process.env.AGENTRUN_RUNTIME_NAMESPACE; try { delete process.env.AGENTRUN_RUNTIME_NAMESPACE; assert.equal(buildSessionPvcSpec({ sessionId: "sess_default_namespace", options: opts }).namespace, "agentrun-v01"); process.env.AGENTRUN_RUNTIME_NAMESPACE = "agentrun-v02"; assert.equal(buildSessionPvcSpec({ sessionId: "sess_runtime_namespace", options: opts }).namespace, "agentrun-v02"); } finally { if (previousRuntimeNamespace === undefined) delete process.env.AGENTRUN_RUNTIME_NAMESPACE; else process.env.AGENTRUN_RUNTIME_NAMESPACE = previousRuntimeNamespace; } const after = await store.getSession(sessionId); assert.equal(after?.storageKind, "pvc"); assert.equal(after?.storagePvcName, summary.pvcName); const readSummary = await getSessionPvcSummary({ store, sessionId, options: opts }); assert.equal(readSummary.pvcName, summary.pvcName); assert.equal(readSummary.pvcPhase, "Bound"); assert.equal(readSummary.storageSizeBytes, 1024 * 1024 * 1024); await refreshSessionPvcSummary({ store, sessionId, summary: { ...summary, storageFilesCount: 7, storageSha256: "abc123" }, options: opts }); const refreshed = await store.getSession(sessionId); assert.equal(refreshed?.storageFilesCount, 7); assert.equal(refreshed?.storageSha256, "abc123"); const evicted = await deleteSessionPvc({ store, sessionId, options: opts }); assert.equal(evicted.storageKind, "evicted"); const evictedSession = await store.getSession(sessionId); assert.equal(evictedSession?.storageKind, "evicted"); assert.ok(evictedSession?.storageEvictedAt); const now = Date.now(); const expiredId = "sess_expired_001"; const activeId = "sess_active_001"; store.upsertSession({ sessionId: expiredId, tenantId: "hwlab", projectId: "pikasTech/HWLAB", backendProfile: "codex", conversationId: null, threadId: null, metadata: {}, expiresAt: new Date(now - 60_000).toISOString(), codexRolloutSubdir: "sessions" }); await store.refreshSessionStorage({ sessionId: expiredId, storageKind: "pvc", pvcName: sessionPvcNameFor(expiredId), codexRolloutSubdir: "sessions" }); store.upsertSession({ sessionId: activeId, tenantId: "hwlab", projectId: "pikasTech/HWLAB", backendProfile: "codex", conversationId: null, threadId: null, metadata: {}, expiresAt: new Date(now - 60_000).toISOString(), codexRolloutSubdir: "sessions" }); await store.refreshSessionStorage({ sessionId: activeId, storageKind: "pvc", pvcName: sessionPvcNameFor(activeId), codexRolloutSubdir: "sessions" }); const activeRun = store.createRun({ tenantId: "hwlab", projectId: "pikasTech/HWLAB", workspaceRef: { kind: "opaque", path: "." }, providerId: "G14", backendProfile: "codex", executionPolicy: { sandbox: "worktree", approval: "never", timeoutMs: 600_000, network: "restricted", secretScope: {} }, traceSink: null, sessionRef: { sessionId: activeId }, resourceBundleRef: null }); store.createCommand(activeRun.id, { type: "turn", payload: { prompt: "test" } }); const cycle = await runSessionStorageGc({ store, options: opts, now, maxSessions: 10 }); assert.ok(cycle.scanned >= 1); assert.ok(cycle.deleted >= 1); const expiredAfter = await store.getSession(expiredId); assert.equal(expiredAfter?.storageKind, "evicted"); const activeAfter = await store.getSession(activeId); assert.equal(activeAfter?.storageKind, "pvc"); const restStore = new MemoryAgentRunStore(); const server = await startManagerServer({ port: 0, host: "127.0.0.1", sourceCommit: "self-test", store: restStore, sessionPvcOptions: { kubectlHandler: fakeKubectl } }); try { const client = new ManagerClient(server.baseUrl); const create = await client.post("/api/v1/sessions", { sessionId: "sess_rest_create_001", tenantId: "hwlab", projectId: "pikasTech/HWLAB", backendProfile: "codex" }) as { action: string; pvc: { pvcName: string; pvcPhase: string } }; assert.equal(create.action, "session-created"); assert.ok(create.pvc.pvcName.startsWith("agentrun-v01-session-")); const existing = await client.post("/api/v1/sessions", { sessionId: "sess_rest_create_001", tenantId: "hwlab", projectId: "pikasTech/HWLAB", backendProfile: "codex" }) as { action: string }; assert.equal(existing.action, "session-exists"); const recovered = await client.post("/api/v1/sessions", { sessionId: "sess_rest_create_002", tenantId: "hwlab", projectId: "pikasTech/HWLAB", backendProfile: "codex" }) as { action: string }; assert.equal(recovered.action, "session-created"); const recoveredFromNone = await client.post("/api/v1/sessions", { sessionId: "sess_rest_create_003", tenantId: "hwlab", projectId: "pikasTech/HWLAB", backendProfile: "codex" }) as { action: string }; assert.equal(recoveredFromNone.action, "session-created"); await restStore.refreshSessionStorage({ sessionId: "sess_rest_create_003", storageKind: "none", codexRolloutSubdir: "sessions" }); const recoveredAgain = await client.post("/api/v1/sessions", { sessionId: "sess_rest_create_003", tenantId: "hwlab", projectId: "pikasTech/HWLAB", backendProfile: "codex" }) as { action: string }; assert.equal(recoveredAgain.action, "session-storage-recovered"); } finally { if (server.server.listening) await new Promise((resolve) => server.server.close(() => resolve())); } return { name: "mgr-session-pvc", tests: ["session-pvc-create-summary-eviction", "session-pvc-gc", "session-pvc-rest-create"] }; }; export default selfTest;