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
14 changes: 8 additions & 6 deletions ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm install
- name: Validate OpenAPI Spec
run: npm run api:validate
- run: npm run lint
- run: npm test
cache: 'pnpm'
- run: pnpm install
- run: pnpm run lint
- run: pnpm test
44 changes: 44 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,50 @@ paths:
$ref: '#/components/schemas/SuccessEnvelope'

/api/attestations:
get:
summary: List Attestations
description: Returns a paginated list of attestations, optionally filtered by commitmentId.
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: pageSize
in: query
schema:
type: integer
default: 10
- name: commitmentId
in: query
schema:
type: string
description: Filter attestations by a specific commitment ID.
responses:
'200':
description: A paginated list of attestations.
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/SuccessEnvelope'
- type: object
properties:
data:
type: object
properties:
items:
type: array
items:
type: object
meta:
type: object
properties:
totalItems: { type: integer }
itemCount: { type: integer }
itemsPerPage: { type: integer }
totalPages: { type: integer }
currentPage: { type: integer }
post:
summary: Record Attestation
description: Logs an attestation event for a commitment (Stub).
Expand Down
53 changes: 0 additions & 53 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,53 +0,0 @@
{
"name": "commitlabs-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest",
"test:watch": "vitest --watch",
"test:coverage": "vitest --coverage",
"seed:mock": "tsx scripts/seed-backend-mock.ts",
"vercel-build": "vercel build",
"vercel-deploy": "vercel --prod"
},
"dependencies": {
"@stellar/freighter-api": "^1.6.0",
"@stellar/stellar-sdk": "^11.2.2",
"@tailwindcss/postcss": "^4.1.18",
"clsx": "^2.1.1",
"framer-motion": "^12.29.2",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.563.0",
"next": "^14.0.0",
"postcss": "^8.5.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.5.0",
"recharts": "^3.7.0",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18",
"zod": "^4.3.6"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.19.33",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitest/coverage-v8": "^4.1.5",
"@vitest/ui": "^4.0.18",
"eslint": "^8.0.0",
"eslint-config-next": "^14.0.0",
"happy-dom": "^20.7.0",
"ioredis": "^5.10.1",
"node-fetch": "^3.3.2",
"tsx": "^4.21.0",
"typescript": "^5.0.0",
"vitest": "^4.0.18"
}
}
66 changes: 66 additions & 0 deletions tests/api/attestations_get.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, it, expect, vi } from 'vitest';
import { NextRequest } from 'next/server';
import { GET } from '@/app/api/attestations/route';

vi.mock('@/lib/backend/mockDb', () => ({
getMockData: vi.fn(async () => ({
attestations: Array.from({ length: 25 }, (_, i) => ({
id: `att-${i}`,
commitmentId: i < 5 ? 'comm-1' : 'comm-2',
type: 'health_check',
})),
})),
}));

vi.mock('@/lib/backend/rateLimit', () => ({
checkRateLimit: vi.fn(async () => true),
}));

describe('GET /api/attestations', () => {
const baseUrl = 'http://localhost/api/attestations';

it('should filter results by commitmentId', async () => {
const req = new NextRequest(`${baseUrl}?commitmentId=comm-1`);
const res = await GET(req);
const body = await res.json();

expect(body.ok).toBe(true);
expect(body.data.items).toHaveLength(5);
expect(body.data.meta.totalItems).toBe(5);
body.data.items.forEach((item: any) => {
expect(item.commitmentId).toBe('comm-1');
});
});

it('should return the correct subset for page 2', async () => {
const req = new NextRequest(`${baseUrl}?page=2&pageSize=10`);
const res = await GET(req);
const body = await res.json();

expect(body.ok).toBe(true);
expect(body.data.items).toHaveLength(10);
expect(body.data.meta.currentPage).toBe(2);
// Offset for page 2, size 10 should start from index 10 (id att-10)
expect(body.data.items[0].id).toBe('att-10');
});

it('should handle out-of-bounds page requests gracefully', async () => {
const req = new NextRequest(`${baseUrl}?page=100`);
const res = await GET(req);
const body = await res.json();

expect(body.ok).toBe(true);
expect(body.data.items).toHaveLength(0);
expect(body.data.meta.currentPage).toBe(100);
expect(body.data.meta.totalPages).toBe(3); // 25 items / 10 per page = 3 pages
});

it('should include the standardized response envelope', async () => {
const req = new NextRequest(baseUrl);
const res = await GET(req);
const body = await res.json();

expect(body).toHaveProperty('ok');
expect(body).toHaveProperty('data');
});
});
2 changes: 1 addition & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ export default defineConfig({
'@': path.resolve(__dirname, './src'),
},
},
})
})
Loading