Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions test/adapters/claude-session-swap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// the command that caused it.
import test from 'node:test'
import assert from 'node:assert/strict'
import { mkdtempSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { readRawCredential, swapCredential } from '../../src/adapters/claude-session/swap.ts'
Expand Down Expand Up @@ -169,20 +169,29 @@ test('a source with no login is refused, and nothing is written', async () => {
})

test('an unwritable target is refused, and says nothing changed', async () => {
// A read-only PARENT, so the target cannot be created inside it. Not
// `/proc/nonexistent/...`: that path throws ENOENT on macOS but makes
// `mkdirSync(recursive)` HANG on Linux, which wedged the whole suite in CI
// the first time this branch met a Linux runner.
const from = fresh()
const fake = fakeSecurity({ [keychainService(from, env)]: BLOB })
// A path whose parent cannot be created.
const result = swapCredential(from, '/proc/nonexistent/swisscode-target', {
env,
platform: 'darwin',
exec: fake.exec,
})
assert.equal(result.ok, false)
assert.equal(
fake.calls.filter((c) => c.args[0] === 'delete-generic-password').length,
0,
'a failed write must never drop the target\'s existing login',
)
const parent = fresh()
chmodSync(parent, 0o500)
try {
const result = swapCredential(from, join(parent, 'target'), {
env,
platform: 'darwin',
exec: fake.exec,
})
assert.equal(result.ok, false)
assert.equal(
fake.calls.filter((c) => c.args[0] === 'delete-generic-password').length,
0,
"a failed write must never drop the target's existing login",
)
} finally {
chmodSync(parent, 0o700)
}
})

test('the identity block is COPIED, or the swap is only half done', async () => {
Expand Down
24 changes: 20 additions & 4 deletions test/adapters/fs-usage-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// fail the command that triggered it.
import test from 'node:test'
import assert from 'node:assert/strict'
import { mkdtempSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { SNAPSHOT_TTL_MS, createFsUsageStore } from '../../src/adapters/store/fs-usage-store.ts'
Expand Down Expand Up @@ -87,9 +87,25 @@ test('a snapshot with nothing usable left in it reads as absent', () => {
})

test('an unwritable directory does not throw — the measurement already happened', () => {
const store = createFsUsageStore({ dir: '/proc/nonexistent/swisscode' })
assert.doesNotThrow(() => store.write({ remaining: { a: 1 }, checkedAt: Date.now() }))
assert.equal(store.read(), null)
// A REAL read-only directory, matching fs-cursor-store.test.ts.
//
// The first version of this used `/proc/nonexistent/...` as a path that
// "obviously cannot be written". On macOS there is no /proc, so it failed
// fast with ENOENT and the test passed. ON LINUX `mkdirSync(recursive)`
// AGAINST THAT PATH HANGS — not throws, hangs, with no output and no exit —
// which wedged the whole suite in CI. Portable fixtures only.
const parent = fresh()
const dir = join(parent, 'state')
mkdirSync(dir)
chmodSync(dir, 0o500)
const store = createFsUsageStore({ dir })
try {
assert.doesNotThrow(() => store.write({ remaining: { a: 1 }, checkedAt: Date.now() }))
assert.equal(store.read(), null)
} finally {
// Restored so the temp tree can be cleaned up by whoever cleans /tmp.
chmodSync(dir, 0o700)
}
})

test('the file holds no credential — only account names and figures', () => {
Expand Down