diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9e8b1a4..2cb71d23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,7 +96,6 @@ jobs: - name: Run Backend Tests run: | ls -la src/generated/prisma - npm install @vitest/coverage-v8@2.1.9 --no-save npx vitest run --coverage --reporter=basic working-directory: backend env: @@ -143,7 +142,7 @@ jobs: run: | docker compose up -d postgres sleep 10 - docker compose run --rm -e DATABASE_URL=postgresql://flowfi:flowfi_dev_password@postgres:5432/flowfi backend npx -y prisma db push --accept-data-loss + docker compose run --rm -e DATABASE_URL=postgresql://flowfi:flowfi_dev_password@postgres:5432/flowfi backend npx -y prisma db push --accept-data-loss --schema=/app/prisma/schema.prisma docker compose up -d backend sleep 15 curl --fail http://localhost:3001/health || (docker compose logs backend && exit 1) diff --git a/backend/Dockerfile b/backend/Dockerfile index d9520066..cb8d7fc5 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,25 +1,16 @@ FROM node:20-alpine@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 AS builder - WORKDIR /app - COPY package*.json ./ RUN npm install - COPY tsconfig.json ./ COPY src ./src COPY prisma ./prisma - RUN npm run build - FROM node:20-alpine@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 AS runner - WORKDIR /app - ENV NODE_ENV=production - COPY package*.json ./ RUN npm install --omit=dev - COPY --from=builder /app/dist ./dist COPY --from=builder /app/src/generated ./dist/generated COPY --from=builder /app/prisma ./prisma @@ -28,7 +19,5 @@ COPY --from=builder /app/prisma ./prisma # check runs `prisma db push` inside this image, so the config must be present # too (dotenv is a runtime dependency, so the config loads). COPY prisma.config.ts ./ - EXPOSE 3001 - -CMD ["npm", "start"] +CMD ["npm", "start"] \ No newline at end of file diff --git a/backend/src/app.ts b/backend/src/app.ts index a2bfe853..e2a173dd 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -113,6 +113,31 @@ app.use((req: Request, res: Response, next: NextFunction) => { return next(); // Not versioned, continue to deprecated handlers }); +// Deprecated route handlers (return 410 Gone) +app.post('/streams', (req: Request, res: Response) => { + res.status(410).json({ + error: 'Deprecated endpoint', + message: 'This endpoint has been deprecated. Please use /v1/streams instead.', + deprecated: true, + migration: { + old: '/streams', + new: '/v1/streams' + } + }); +}); + +app.post('/events', (req: Request, res: Response) => { + res.status(410).json({ + error: 'Deprecated endpoint', + message: 'This endpoint has been deprecated. Please use /v1/events instead.', + deprecated: true, + migration: { + old: '/events', + new: '/v1/events' + } + }); +}); + // Health check routes app.use('/health', healthRoutes); diff --git a/backend/src/workers/soroban-event-worker.ts b/backend/src/workers/soroban-event-worker.ts index 6c9b9aa4..afdb3566 100644 --- a/backend/src/workers/soroban-event-worker.ts +++ b/backend/src/workers/soroban-event-worker.ts @@ -975,4 +975,4 @@ export class SorobanEventWorker { } } -export const sorobanEventWorker = new SorobanEventWorker(); +export const sorobanEventWorker = new SorobanEventWorker(); \ No newline at end of file diff --git a/backend/tests/deprecated.test.ts b/backend/tests/deprecated.test.ts index fa205da6..99d11648 100644 --- a/backend/tests/deprecated.test.ts +++ b/backend/tests/deprecated.test.ts @@ -5,22 +5,26 @@ import app from '../src/app.js'; // This file tests deprecated endpoints WITHOUT any Prisma mocking // so the real route handlers respond directly without interference. -describe('Removed unversioned routes', () => { - it('POST /streams returns 404 Not Found', async () => { +describe('Deprecated route responses', () => { + it('POST /streams returns 410 Gone', async () => { const response = await request(app) .post('/streams') .send({}) .set('Accept', 'application/json'); - expect(response.status).toBe(404); + expect(response.status).toBe(410); + expect(response.body.deprecated).toBe(true); + expect(response.body.migration).toMatchObject({ old: '/streams', new: '/v1/streams' }); }); - it('POST /events returns 404 Not Found', async () => { + it('POST /events returns 410 Gone', async () => { const response = await request(app) .post('/events') .send({}) .set('Accept', 'application/json'); - expect(response.status).toBe(404); + expect(response.status).toBe(410); + expect(response.body.deprecated).toBe(true); + expect(response.body.migration).toMatchObject({ old: '/events', new: '/v1/events' }); }); }); diff --git a/contracts/stream_contract/src/test.rs b/contracts/stream_contract/src/test.rs index 9c20d170..b3b3e895 100644 --- a/contracts/stream_contract/src/test.rs +++ b/contracts/stream_contract/src/test.rs @@ -180,6 +180,20 @@ fn test_update_fee_config_rejects_invalid_fee_rate() { assert_eq!(result, Err(Ok(StreamError::InvalidFeeRate))); } +#[test] +fn test_update_fee_config_rejects_not_initialized() { + let env = Env::default(); + env.mock_all_auths(); + let client = create_contract(&env); + + let admin = Address::generate(&env); + let treasury = Address::generate(&env); + + // Call update_fee_config without calling initialize first + let result = client.try_update_fee_config(&admin, &treasury, &100); + assert_eq!(result, Err(Ok(StreamError::NotInitialized))); +} + #[test] fn test_initialize_emits_event() { let env = Env::default();