refactor!: simplify runtime coordination #7
Keep financial ownership, atomic publication and refresh CAS. Failed refresh leaves cookies untouched; logout still clears them. Remove full-history candle prefetch and special metadata retries. Use native Pino request context instead of parallel middleware. BREAKING CHANGE: remove investment stateVersion, queued sync state, and tbank-sync status progress/resources. Consumers must observe resources through sync=observe with the updated frontend contract.
This commit is contained in:
+27
-17
@@ -1,12 +1,13 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
import { PARAMS_PROVIDER_TOKEN } from 'nestjs-pino';
|
||||
import request from 'supertest';
|
||||
import { App } from 'supertest/types';
|
||||
|
||||
import { AppModule } from '../src/app.module';
|
||||
import { StructuredLogger } from '../src/common/logging/structured-logger.service';
|
||||
import { loggingOptions } from '../src/common/logging/logging.module';
|
||||
import { configureApp } from '../src/main';
|
||||
import { AuthService } from '../src/modules/auth/application/auth.service';
|
||||
import { PrismaService } from '../src/prisma/prisma.service';
|
||||
@@ -16,10 +17,22 @@ type Problem = { code: string };
|
||||
|
||||
describe('Authentication (e2e)', () => {
|
||||
let app: INestApplication<App>;
|
||||
const logs: Record<string, unknown>[] = [];
|
||||
|
||||
beforeEach(async () => {
|
||||
logs.length = 0;
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).overrideProvider(PARAMS_PROVIDER_TOKEN).useFactory({
|
||||
inject: [ConfigService],
|
||||
factory: (config: Parameters<typeof loggingOptions>[0]) => ({
|
||||
pinoHttp: {
|
||||
...loggingOptions(config),
|
||||
level: 'info',
|
||||
stream: { write: (line: string) => logs.push(JSON.parse(line) as Record<string, unknown>) },
|
||||
},
|
||||
assignResponse: true,
|
||||
}),
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
@@ -187,10 +200,12 @@ describe('Authentication (e2e)', () => {
|
||||
]);
|
||||
const winner = responses.find((response) => response.status === 200);
|
||||
if (!winner) throw new Error('Expected one successful rotation');
|
||||
expect(responses.find((response) => response.status === 401)?.headers['set-cookie']).toBeUndefined();
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/v1/auth/refresh')
|
||||
.set('Cookie', cookie)
|
||||
.expect(401);
|
||||
.expect(401)
|
||||
.expect((response) => expect(response.headers['set-cookie']).toBeUndefined());
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/v1/auth/refresh')
|
||||
.set('Cookie', winner.headers['set-cookie'][0])
|
||||
@@ -304,8 +319,6 @@ describe('Authentication (e2e)', () => {
|
||||
});
|
||||
|
||||
it('exposes a server request ID and logs one completion event', async () => {
|
||||
const logger = app.get(StructuredLogger);
|
||||
const event = jest.spyOn(logger, 'event');
|
||||
const response = await request(app.getHttpServer())
|
||||
.get('/api/v1/auth/me')
|
||||
.set('Origin', 'http://localhost:5173')
|
||||
@@ -315,16 +328,12 @@ describe('Authentication (e2e)', () => {
|
||||
expect(response.headers['access-control-expose-headers']).toContain(
|
||||
'X-Request-ID',
|
||||
);
|
||||
expect(event).toHaveBeenCalledTimes(1);
|
||||
expect(event).toHaveBeenCalledWith(
|
||||
'warn',
|
||||
'http.request.completed',
|
||||
expect.objectContaining({ status: 401, code: 'UNAUTHORIZED' }),
|
||||
);
|
||||
expect(logs.filter((entry) => entry.requestId === response.headers['x-request-id'])).toEqual([
|
||||
expect.objectContaining({ level: 40, message: 'http.request.completed', status: 401, code: 'UNAUTHORIZED' }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('logs an unhandled error with safe public context and correlation', async () => {
|
||||
const error = jest.spyOn(PinoLogger.prototype, 'error');
|
||||
const clientRequestId = 'a2dc6fbb-5a48-4c3c-9e3e-4fbf4d638a43';
|
||||
jest
|
||||
.spyOn(app.get(AuthService), 'login')
|
||||
@@ -342,17 +351,18 @@ describe('Authentication (e2e)', () => {
|
||||
expect(body).toEqual({ status: 500, code: 'INTERNAL_SERVER_ERROR' }),
|
||||
);
|
||||
|
||||
expect(error).toHaveBeenCalledTimes(1);
|
||||
expect(error).toHaveBeenCalledWith(
|
||||
const completed = logs.filter((entry) => entry.requestId === response.headers['x-request-id']);
|
||||
expect(completed).toEqual([
|
||||
expect.objectContaining({
|
||||
requestId: response.headers['x-request-id'],
|
||||
clientRequestId,
|
||||
status: 500,
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
level: 50,
|
||||
message: 'http.request.completed',
|
||||
}),
|
||||
'http.request.completed',
|
||||
);
|
||||
expect(JSON.stringify(error.mock.calls)).not.toContain(
|
||||
]);
|
||||
expect(JSON.stringify(logs)).not.toContain(
|
||||
'SYNTHETIC_AUDIT_SECRET',
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user