fix(federation/client): serialize cache fills, destroy evicted Agent, cover env-var guard
- HIGH-A: resolveEntry now uses promise-cache pattern so concurrent callers serialize on a single in-flight build, eliminating duplicate key material in heap and duplicate DB round-trips - HIGH-B: flushPeer destroys the evicted undici Agent so stale TLS connections close on cert rotation - MED-C: add regression test for PEER_MISCONFIGURED when STEP_CA_ROOT_CERT_PATH is unset Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -442,6 +442,37 @@ describe('FederationClientService', () => {
|
||||
expect(selectSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('serializes concurrent resolveEntry calls — only one DB lookup', async () => {
|
||||
const db = makeDb();
|
||||
const selectSpy = vi.spyOn(db, 'select');
|
||||
const svc = new FederationClientService(db);
|
||||
const resolveEntry = (
|
||||
svc as unknown as {
|
||||
resolveEntry: (peerId: string) => Promise<unknown>;
|
||||
}
|
||||
).resolveEntry.bind(svc);
|
||||
|
||||
const [a, b] = await Promise.all([resolveEntry(PEER_ID), resolveEntry(PEER_ID)]);
|
||||
expect(a).toBe(b);
|
||||
expect(selectSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('flushPeer destroys the evicted Agent so old TLS connections close', async () => {
|
||||
const db = makeDb();
|
||||
const svc = new FederationClientService(db);
|
||||
const resolveEntry = (
|
||||
svc as unknown as {
|
||||
resolveEntry: (peerId: string) => Promise<{ agent: { destroy: () => Promise<void> } }>;
|
||||
}
|
||||
).resolveEntry.bind(svc);
|
||||
|
||||
const entry = await resolveEntry(PEER_ID);
|
||||
const destroySpy = vi.spyOn(entry.agent, 'destroy').mockResolvedValue();
|
||||
|
||||
svc.flushPeer(PEER_ID);
|
||||
expect(destroySpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('flushPeer() invalidates cache — next call re-reads DB', async () => {
|
||||
const db = makeDb();
|
||||
const { mockAgent, pool } = makeMockAgent();
|
||||
@@ -474,6 +505,25 @@ describe('FederationClientService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── loadStepCaRoot env-var guard ─────────────────────────────────────────
|
||||
|
||||
describe('loadStepCaRoot() env-var guard', () => {
|
||||
it('throws PEER_MISCONFIGURED when STEP_CA_ROOT_CERT_PATH is not set', async () => {
|
||||
delete process.env['STEP_CA_ROOT_CERT_PATH'];
|
||||
const db = makeDb();
|
||||
const svc = new FederationClientService(db);
|
||||
const resolveEntry = (
|
||||
svc as unknown as {
|
||||
resolveEntry: (peerId: string) => Promise<unknown>;
|
||||
}
|
||||
).resolveEntry.bind(svc);
|
||||
|
||||
await expect(resolveEntry(PEER_ID)).rejects.toMatchObject({
|
||||
code: 'PEER_MISCONFIGURED',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── FederationClientError class ──────────────────────────────────────────
|
||||
|
||||
describe('FederationClientError', () => {
|
||||
|
||||
Reference in New Issue
Block a user