Skip to content
Open
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
32 changes: 28 additions & 4 deletions src/__tests__/harness/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextRequest } from "next/server";
import { vi } from "vitest";
import { getServerSession } from "next-auth/next";
import { MIDDLEWARE_HEADERS } from "@/config/middleware";

export interface InvokeRouteOptions {
method?: string;
Expand All @@ -9,6 +10,7 @@ export interface InvokeRouteOptions {
body?: unknown;
headers?: HeadersInit;
params?: Record<string, string | string[]> | Promise<Record<string, string | string[]>>;
useMiddlewareAuth?: boolean;
}

export interface InvokeRouteResult {
Expand Down Expand Up @@ -54,11 +56,33 @@ export async function invokeRoute(
handler: RouteHandler,
options: InvokeRouteOptions = {},
): Promise<InvokeRouteResult> {
const request = createRequest(options);
let request = createRequest(options);

vi.mocked(getServerSession).mockResolvedValue(
options.session === undefined ? null : options.session,
);
// Support middleware authentication if requested
if (options.useMiddlewareAuth && options.session) {
const session = options.session as { user?: { id?: string; email?: string; name?: string } };
if (session.user) {
const headers = new Headers(request.headers);
headers.set(MIDDLEWARE_HEADERS.USER_ID, session.user.id || "");
headers.set(MIDDLEWARE_HEADERS.USER_EMAIL, session.user.email || "");
headers.set(MIDDLEWARE_HEADERS.USER_NAME, session.user.name || "");
headers.set(MIDDLEWARE_HEADERS.AUTH_STATUS, "authenticated");
headers.set(MIDDLEWARE_HEADERS.REQUEST_ID, crypto.randomUUID());

request = new NextRequest(request.url, {
method: request.method,
headers,
body: request.body,
// @ts-ignore - duplex is needed for body streaming
duplex: request.body ? "half" : undefined,
});
}
} else {
// Legacy NextAuth session mocking for routes not yet migrated
vi.mocked(getServerSession).mockResolvedValue(
options.session === undefined ? null : options.session,
);
}

const context = options.params
? {
Expand Down
17 changes: 11 additions & 6 deletions src/__tests__/integration/api/janitors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
createGetRequest,
createPostRequest,
createPutRequest,
createAuthenticatedPostRequest,
getMockedSession,
} from "@/__tests__/support/helpers";

Expand Down Expand Up @@ -780,10 +781,12 @@ describe("Janitor API Integration Tests", () => {
status: "PENDING",
},
});

getMockedSession().mockResolvedValue(createAuthenticatedSession(user) as any);

const request = createPostRequest("http://localhost/api/test", {});
const request = createAuthenticatedPostRequest("http://localhost/api/test", {}, {
id: user.id,
email: user.email || "",
name: user.name || "",
});

const response = await AcceptRecommendation(request, {
params: Promise.resolve({ id: recommendation.id }),
Expand Down Expand Up @@ -842,11 +845,13 @@ describe("Janitor API Integration Tests", () => {
status: "PENDING",
},
});

getMockedSession().mockResolvedValue(createAuthenticatedSession(user) as any);

const request = createPostRequest("http://localhost/api/test", {
const request = createAuthenticatedPostRequest("http://localhost/api/test", {
reason: "Not relevant for this project",
}, {
id: user.id,
email: user.email || "",
name: user.name || "",
});

const response = await DismissRecommendation(request, {
Expand Down
60 changes: 33 additions & 27 deletions src/__tests__/integration/api/stakwork-create-customer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import { POST } from "@/app/api/stakwork/create-customer/route";
import { db } from "@/lib/db";
import { EncryptionService } from "@/lib/encryption";
import {
createAuthenticatedSession,
generateUniqueId,
generateUniqueSlug,
createPostRequest,
getMockedSession,
createAuthenticatedPostRequest,
} from "@/__tests__/support/helpers";

// Mock stakwork service factory to capture calls
Expand Down Expand Up @@ -102,19 +101,23 @@ const setupTestData = async (options: {
describe("POST /api/stakwork/create-customer", () => {
let workspaceId: string;
let PLAINTEXT_SWARM_API_KEY: string;
let testUser: { id: string; email: string | null; name: string | null };

beforeEach(async () => {
vi.clearAllMocks();

const { testData, PLAINTEXT_SWARM_API_KEY: plainTextKey } = await setupTestData();
workspaceId = testData.workspace.id;
PLAINTEXT_SWARM_API_KEY = plainTextKey;

getMockedSession().mockResolvedValue(createAuthenticatedSession(testData.user));
testUser = testData.user;
});

it("creates secret with plaintext value (not encrypted JSON)", async () => {
const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest(
"http://localhost:3000/api/stakwork/create-customer",
{ workspaceId },
{ id: testUser.id, email: testUser.email || "", name: testUser.name || "" }
);

const res = await POST(req);

Expand All @@ -133,7 +136,11 @@ describe("POST /api/stakwork/create-customer", () => {
data: { swarmApiKey: JSON.stringify(doubleCipher) },
});

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest(
"http://localhost:3000/api/stakwork/create-customer",
{ workspaceId },
{ id: testUser.id, email: testUser.email || "", name: testUser.name || "" }
);

mockCreateSecret.mockClear();
const res = await POST(req);
Expand All @@ -144,20 +151,22 @@ describe("POST /api/stakwork/create-customer", () => {

describe("authentication failures", () => {
it("returns 401 when user is not authenticated", async () => {
getMockedSession().mockResolvedValue(null);

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });

const res = await POST(req);
expect(res?.status).toBe(401);

const json = await res.json();
expect(json).toEqual({ error: "Unauthorized" });
expect(json.error).toBe("Unauthorized");
expect(mockCreateCustomer).not.toHaveBeenCalled();
});

it("returns error when workspaceId is missing from request body", async () => {
const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", {});
const req = createAuthenticatedPostRequest(
"http://localhost:3000/api/stakwork/create-customer",
{},
{ id: testUser.id, email: testUser.email || "", name: testUser.name || "" }
);

const res = await POST(req);

Expand All @@ -174,9 +183,9 @@ describe("POST /api/stakwork/create-customer", () => {
});

const nonExistentWorkspaceId = generateUniqueId("nonexistent");
const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", {
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", {
workspaceId: nonExistentWorkspaceId,
});
}, testUser);

const res = await POST(req);

Expand All @@ -194,16 +203,13 @@ describe("POST /api/stakwork/create-customer", () => {

it("handles swarm not found by skipping secret creation", async () => {
// Create workspace without swarm using helper
const { testData: userData } = await setupTestData({ includeSwarm: false });

getMockedSession().mockResolvedValue(createAuthenticatedSession(userData.user));
mockCreateCustomer.mockResolvedValueOnce({
const { testData: userData } = await setupTestData({ includeSwarm: false }); mockCreateCustomer.mockResolvedValueOnce({
data: { token: "stak-token-no-swarm" },
});

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", {
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", {
workspaceId: userData.workspace.id,
});
}, testUser);

const res = await POST(req);
expect(res?.status).toBe(201);
Expand All @@ -219,7 +225,7 @@ describe("POST /api/stakwork/create-customer", () => {
data: { message: "Customer created but no token" },
});

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId }, testUser);

const res = await POST(req);
await expectErrorResponse(res, 500, { error: "Invalid response from Stakwork API" });
Expand All @@ -230,7 +236,7 @@ describe("POST /api/stakwork/create-customer", () => {
message: "Success",
});

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId }, testUser);

const res = await POST(req);
await expectErrorResponse(res, 500, { error: "Invalid response from Stakwork API" });
Expand All @@ -246,7 +252,7 @@ describe("POST /api/stakwork/create-customer", () => {

mockCreateCustomer.mockRejectedValueOnce(apiError);

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId }, testUser);

const res = await POST(req);
await expectErrorResponse(res, 503, {
Expand All @@ -265,7 +271,7 @@ describe("POST /api/stakwork/create-customer", () => {
new Error("Failed to create secret on Stakwork")
);

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId }, testUser);

const res = await POST(req);

Expand All @@ -279,7 +285,7 @@ describe("POST /api/stakwork/create-customer", () => {
new Error("Network timeout")
);

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId }, testUser);

const res = await POST(req);
await expectErrorResponse(res, 500, { error: "Failed to create customer" });
Expand All @@ -298,7 +304,7 @@ describe("POST /api/stakwork/create-customer", () => {
data: { token: "stak-token-no-alias" },
});

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId }, testUser);

const res = await POST(req);
expect(res?.status).toBe(201);
Expand All @@ -318,7 +324,7 @@ describe("POST /api/stakwork/create-customer", () => {
data: { token: "stak-token-null-key" },
});

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId }, testUser);

const res = await POST(req);
expect(res?.status).toBe(201);
Expand All @@ -332,7 +338,7 @@ describe("POST /api/stakwork/create-customer", () => {
data: { token: "stak-token-sanitize" },
});

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId }, testUser);

const res = await POST(req);
expect(res?.status).toBe(201);
Expand All @@ -353,7 +359,7 @@ describe("POST /api/stakwork/create-customer", () => {
data: { token: stakworkToken },
});

const req = createPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId });
const req = createAuthenticatedPostRequest("http://localhost:3000/api/stakwork/create-customer", { workspaceId }, testUser);

const res = await POST(req);
expect(res?.status).toBe(201);
Expand Down
Loading
Loading