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
108 changes: 108 additions & 0 deletions tests/e2e/credential-batch-mint.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
55 changes: 55 additions & 0 deletions tests/e2e/pending-rewards.test.ts
Original file line number Diff line number Diff line change
@@ -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");
}
});
});
58 changes: 58 additions & 0 deletions tests/e2e/refresh-token.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
85 changes: 85 additions & 0 deletions tests/e2e/reward-transactions.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});