test: replace metadata sleeps with upstream start barriers (WealthLedger/backend#63) #102
@@ -129,13 +129,17 @@ describe('durable shared instrument metadata (SQLite)', () => {
|
|||||||
});
|
});
|
||||||
it('coalesces twenty requests across users while giving each user a private handle', async () => {
|
it('coalesces twenty requests across users while giving each user a private handle', async () => {
|
||||||
const blocked = deferred<TbankInstrument>();
|
const blocked = deferred<TbankInstrument>();
|
||||||
upstream.getInstrumentByUid.mockReturnValue(blocked.promise);
|
const started = deferred<void>();
|
||||||
|
upstream.getInstrumentByUid.mockImplementation(() => {
|
||||||
|
started.resolve();
|
||||||
|
return blocked.promise;
|
||||||
|
});
|
||||||
const responses = await Promise.all(
|
const responses = await Promise.all(
|
||||||
Array.from({ length: 20 }, (_, i) =>
|
Array.from({ length: 20 }, (_, i) =>
|
||||||
service.read(i % 2 ? 'owner' : 'other', identity),
|
service.read(i % 2 ? 'owner' : 'other', identity),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await delay(10);
|
await started.promise;
|
||||||
expect(upstream.getInstrumentByUid).toHaveBeenCalledTimes(1);
|
expect(upstream.getInstrumentByUid).toHaveBeenCalledTimes(1);
|
||||||
const ids = new Set(responses.map((response) => response?.work?.id));
|
const ids = new Set(responses.map((response) => response?.work?.id));
|
||||||
expect(ids.size).toBe(2);
|
expect(ids.size).toBe(2);
|
||||||
@@ -151,9 +155,13 @@ describe('durable shared instrument metadata (SQLite)', () => {
|
|||||||
'discards a producer after %s, including shared freshness, without borrowing another user token',
|
'discards a producer after %s, including shared freshness, without borrowing another user token',
|
||||||
async (action) => {
|
async (action) => {
|
||||||
const blocked = deferred<TbankInstrument>();
|
const blocked = deferred<TbankInstrument>();
|
||||||
upstream.getInstrumentByUid.mockReturnValueOnce(blocked.promise);
|
const started = deferred<void>();
|
||||||
|
upstream.getInstrumentByUid.mockImplementationOnce(() => {
|
||||||
|
started.resolve();
|
||||||
|
return blocked.promise;
|
||||||
|
});
|
||||||
await service.read('owner', identity);
|
await service.read('owner', identity);
|
||||||
await delay(10);
|
await started.promise;
|
||||||
await service.read('other', identity);
|
await service.read('other', identity);
|
||||||
await prisma.tbankConnection.delete({ where: { userId: 'owner' } });
|
await prisma.tbankConnection.delete({ where: { userId: 'owner' } });
|
||||||
if (action === 'replace') {
|
if (action === 'replace') {
|
||||||
@@ -187,9 +195,13 @@ describe('durable shared instrument metadata (SQLite)', () => {
|
|||||||
);
|
);
|
||||||
it('does not turn initiating credentials failure into a shared negative lookup or a foreign credentials error', async () => {
|
it('does not turn initiating credentials failure into a shared negative lookup or a foreign credentials error', async () => {
|
||||||
const blocked = deferred<TbankInstrument>();
|
const blocked = deferred<TbankInstrument>();
|
||||||
upstream.getInstrumentByUid.mockReturnValueOnce(blocked.promise);
|
const started = deferred<void>();
|
||||||
|
upstream.getInstrumentByUid.mockImplementationOnce(() => {
|
||||||
|
started.resolve();
|
||||||
|
return blocked.promise;
|
||||||
|
});
|
||||||
await service.read('owner', identity);
|
await service.read('owner', identity);
|
||||||
await delay(10);
|
await started.promise;
|
||||||
await service.read('other', identity);
|
await service.read('other', identity);
|
||||||
blocked.reject(new Error('INVALID_TBANK_TOKEN'));
|
blocked.reject(new Error('INVALID_TBANK_TOKEN'));
|
||||||
await settled();
|
await settled();
|
||||||
@@ -226,7 +238,6 @@ describe('durable shared instrument metadata (SQLite)', () => {
|
|||||||
expect(failed?.fetchedAt).toEqual(old);
|
expect(failed?.fetchedAt).toEqual(old);
|
||||||
if (!failedHandle) throw new Error('Expected forced refresh work');
|
if (!failedHandle) throw new Error('Expected forced refresh work');
|
||||||
expect((await prisma.tbankSyncWork.findUniqueOrThrow({ where: { id: failedHandle } })).state).toBe('failed');
|
expect((await prisma.tbankSyncWork.findUniqueOrThrow({ where: { id: failedHandle } })).state).toBe('failed');
|
||||||
await delay(20);
|
|
||||||
expect(upstream.getInstrumentByUid).toHaveBeenCalledTimes(calls + 1);
|
expect(upstream.getInstrumentByUid).toHaveBeenCalledTimes(calls + 1);
|
||||||
});
|
});
|
||||||
it('coalesces forced refresh and retains freshness/timestamp when it fails before the TTL', async () => {
|
it('coalesces forced refresh and retains freshness/timestamp when it fails before the TTL', async () => {
|
||||||
@@ -285,7 +296,7 @@ describe('durable shared instrument metadata (SQLite)', () => {
|
|||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
service = createService();
|
service = createService();
|
||||||
await service.read('owner', identity, true);
|
await service.read('owner', identity, true);
|
||||||
await delay(10);
|
// Observation starts no work, so a no-call assertion holds without waiting.
|
||||||
expect(upstream.getInstrumentByUid).not.toHaveBeenCalled();
|
expect(upstream.getInstrumentByUid).not.toHaveBeenCalled();
|
||||||
await service.read('owner', identity);
|
await service.read('owner', identity);
|
||||||
await settled();
|
await settled();
|
||||||
|
|||||||
@@ -151,8 +151,12 @@ describe('Request-driven operations synchronization (SQLite)', () => {
|
|||||||
|
|
||||||
it('joins overlapping account and instrument refresh into one atomic publication group', async () => {
|
it('joins overlapping account and instrument refresh into one atomic publication group', async () => {
|
||||||
const gate = deferred<TbankOperationsByCursorResponse>();
|
const gate = deferred<TbankOperationsByCursorResponse>();
|
||||||
|
const started = deferred<void>();
|
||||||
upstream.getOperationsByCursor
|
upstream.getOperationsByCursor
|
||||||
.mockReturnValueOnce(gate.promise)
|
.mockImplementationOnce(() => {
|
||||||
|
started.resolve();
|
||||||
|
return gate.promise;
|
||||||
|
})
|
||||||
.mockResolvedValue({
|
.mockResolvedValue({
|
||||||
hasNext: false,
|
hasNext: false,
|
||||||
items: [{ ...operation(), instrumentUid: 'uid' }],
|
items: [{ ...operation(), instrumentUid: 'uid' }],
|
||||||
@@ -164,7 +168,7 @@ describe('Request-driven operations synchronization (SQLite)', () => {
|
|||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await delay(5);
|
await started.promise;
|
||||||
const whole = requiredHandle(
|
const whole = requiredHandle(
|
||||||
await service.request('owner', [january], true),
|
await service.request('owner', [january], true),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user