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
3 changes: 2 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"private": true,
"scripts": {
"dev": "bun --hot src/index.ts",
"worker": "bun --hot src/modules/worker.ts"
"worker": "bun --hot src/modules/worker.ts",
"test": "bun test src"
},
"devDependencies": {
"@types/bun": "latest",
Expand Down
154 changes: 154 additions & 0 deletions apps/backend/src/modules/middleware.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { beforeEach, describe, expect, test } from "bun:test";
import type { NextFunction, Request, Response } from "express";
import jwt from "jsonwebtoken";
import { auth, internalAuth, type AuthRequest } from "./middleware";

const JWT_SECRET = "test-jwt-secret";
const INTERNAL_TOKEN = "test-internal-token";

function makeReq(authorization?: string): AuthRequest {
return { headers: authorization ? { authorization } : {} } as AuthRequest;
}

function makeRes() {
const sent: { status?: number; body?: unknown } = {};
const res = {
status(code: number) {
sent.status = code;
return this;
},
json(body: unknown) {
sent.body = body;
return this;
},
} as unknown as Response;
return { res, sent };
}

function makeNext() {
const calls: unknown[][] = [];
const next = ((...args: unknown[]) => {
calls.push(args);
}) as unknown as NextFunction;
return { next, calls };
}

beforeEach(() => {
process.env.JWT_SECRET = JWT_SECRET;
process.env.INTERNAL_SERVICE_TOKEN = INTERNAL_TOKEN;
});

describe("auth", () => {
test("attaches the decoded user and calls next for a valid JWT", () => {
const token = jwt.sign({ id: "u1", email: "user@example.com" }, JWT_SECRET);
const req = makeReq(`Bearer ${token}`);
const { res, sent } = makeRes();
const { next, calls } = makeNext();

auth(req, res, next);

expect(calls).toHaveLength(1);
expect(req.user).toMatchObject({ id: "u1", email: "user@example.com" });
expect(sent.status).toBeUndefined();
});

test("lets the internal service token through without a user", () => {
const req = makeReq(`Bearer ${INTERNAL_TOKEN}`);
const { res } = makeRes();
const { next, calls } = makeNext();

auth(req, res, next);

expect(calls).toHaveLength(1);
expect(req.user).toBeUndefined();
});

test.each([
["a missing Authorization header", undefined],
["a non-Bearer scheme", "Basic dXNlcjpwYXNz"],
["a lowercase bearer prefix", "bearer sometoken"],
])("401s on %s", (_label, header) => {
const { res, sent } = makeRes();
const { next, calls } = makeNext();

auth(makeReq(header), res, next);

expect(calls).toHaveLength(0);
expect(sent.status).toBe(401);
expect(sent.body).toEqual({ message: "Unauthorized" });
});

test("401s with 'Invalid token' for a token signed with the wrong secret", () => {
const token = jwt.sign({ id: "u1", email: "user@example.com" }, "other-secret");
const { res, sent } = makeRes();
const { next, calls } = makeNext();

auth(makeReq(`Bearer ${token}`), res, next);

expect(calls).toHaveLength(0);
expect(sent.status).toBe(401);
expect(sent.body).toEqual({ message: "Invalid token" });
});

test("401s with 'Invalid token' for an expired token", () => {
const token = jwt.sign({ id: "u1", email: "user@example.com" }, JWT_SECRET, {
expiresIn: "-1s",
});
const { res, sent } = makeRes();
const { next } = makeNext();

auth(makeReq(`Bearer ${token}`), res, next);

expect(sent.status).toBe(401);
expect(sent.body).toEqual({ message: "Invalid token" });
});

test("401s with 'Invalid token' for garbage after Bearer", () => {
const { res, sent } = makeRes();
const { next } = makeNext();

auth(makeReq("Bearer not-a-jwt"), res, next);

expect(sent.status).toBe(401);
expect(sent.body).toEqual({ message: "Invalid token" });
});
});

describe("internalAuth", () => {
test("calls next when the shared secret matches", () => {
const { res, sent } = makeRes();
const { next, calls } = makeNext();

internalAuth(makeReq(`Bearer ${INTERNAL_TOKEN}`) as Request, res, next);

expect(calls).toHaveLength(1);
expect(sent.status).toBeUndefined();
});

test.each([
["no Authorization header", undefined],
["a non-Bearer scheme", `Basic ${INTERNAL_TOKEN}`],
["the wrong secret", "Bearer wrong-token"],
["an empty Bearer token", "Bearer "],
])("401s on %s", (_label, header) => {
const { res, sent } = makeRes();
const { next, calls } = makeNext();

internalAuth(makeReq(header) as Request, res, next);

expect(calls).toHaveLength(0);
expect(sent.status).toBe(401);
expect(sent.body).toEqual({ message: "Unauthorized" });
});

test("rejects a valid user JWT — this path is service-to-service only", () => {
const token = jwt.sign({ id: "u1", email: "user@example.com" }, JWT_SECRET);
const { res, sent } = makeRes();
const { next, calls } = makeNext();

internalAuth(makeReq(`Bearer ${token}`) as Request, res, next);

expect(calls).toHaveLength(0);
expect(sent.status).toBe(401);
});
});
127 changes: 127 additions & 0 deletions apps/backend/src/modules/user.helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { describe, expect, test } from "bun:test";
import jwt from "jsonwebtoken";
import { isValidEmail, isValidPassword, signUserToken, toPublicUser } from "./user.helpers";

describe("isValidEmail", () => {
test.each([
"user@example.com",
"first.last@sub.domain.co.in",
"user+tag@example.io",
])("accepts %p", (email) => {
expect(isValidEmail(email)).toBe(true);
});

test.each([
"",
"user",
"user@",
"@example.com",
"user@example",
"user @example.com",
"user@exam ple.com",
"two@at@example.com",
])("rejects %p", (email) => {
expect(isValidEmail(email)).toBe(false);
});

test("rejects non-string values", () => {
for (const value of [undefined, null, 42, {}, ["user@example.com"]]) {
expect(isValidEmail(value)).toBe(false);
}
});
});

describe("isValidPassword", () => {
test("accepts a password of exactly the 8 character minimum", () => {
expect(isValidPassword("12345678")).toBe(true);
});

test("rejects a password shorter than 8 characters", () => {
expect(isValidPassword("1234567")).toBe(false);
expect(isValidPassword("")).toBe(false);
});

test("rejects non-string values", () => {
for (const value of [undefined, null, 12345678, { password: "12345678" }]) {
expect(isValidPassword(value)).toBe(false);
}
});
});

describe("signUserToken", () => {
const secret = "test-secret";

test("signs id and email into a token verifiable with JWT_SECRET", () => {
process.env.JWT_SECRET = secret;

const token = signUserToken({ id: "u1", email: "user@example.com" });
const payload = jwt.verify(token, secret) as {
id: string;
email: string;
exp: number;
iat: number;
};

expect(payload.id).toBe("u1");
expect(payload.email).toBe("user@example.com");
expect(payload.exp - payload.iat).toBe(7 * 24 * 60 * 60);
});

test("produces a token that fails verification under a different secret", () => {
process.env.JWT_SECRET = secret;
const token = signUserToken({ id: "u1", email: "user@example.com" });

expect(() => jwt.verify(token, "other-secret")).toThrow();
});

test("does not leak fields beyond id and email", () => {
process.env.JWT_SECRET = secret;
const token = signUserToken({
id: "u1",
email: "user@example.com",
password: "hashed",
} as { id: string; email: string });

expect(Object.keys(jwt.decode(token) as object).sort()).toEqual([
"email",
"exp",
"iat",
"id",
]);
});
});

describe("toPublicUser", () => {
test("keeps only the publicly safe fields", () => {
const createdAt = new Date("2026-01-01T00:00:00.000Z");
const user = {
id: "u1",
email: "user@example.com",
name: "Ada",
createdAt,
password: "hashed-secret",
googleId: "g1",
};

const publicUser = toPublicUser(user);

expect(publicUser).toEqual({
id: "u1",
email: "user@example.com",
name: "Ada",
createdAt,
});
expect(Object.keys(publicUser)).not.toContain("password");
});

test("preserves a null name", () => {
expect(
toPublicUser({
id: "u1",
email: "user@example.com",
name: null,
createdAt: new Date(0),
}).name,
).toBeNull();
});
});
2 changes: 2 additions & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"build": "tsc && vite build",
"lint": "eslint .",
"check-types": "tsc --noEmit",
"test": "bun test src",
"preview": "vite preview"
},
"dependencies": {
Expand All @@ -28,6 +29,7 @@
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"@types/bun": "latest",
"@tailwindcss/vite": "^4.3.3",
"@types/node": "^26.1.1",
"@types/react": "^19.2.17",
Expand Down
62 changes: 62 additions & 0 deletions apps/frontend/src/features/build/describeEvent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, expect, test } from "bun:test";
import type { OrchestratorEvent } from "../../../../../packages/agents/agent/events";
import { describeEvent } from "./describeEvent";

describe("describeEvent", () => {
test("describes the orchestrator lifecycle events", () => {
expect(describeEvent({ type: "orchestrator_agent_started" })).toBe("Planning the build…");
expect(describeEvent({ type: "main_agent_success" })).toBe("Agent finished its task.");
expect(describeEvent({ type: "orchestrator_completed", summary: "Shipped the app." })).toBe(
"Shipped the app.",
);
});

test("names the tool for a main agent tool call", () => {
expect(describeEvent({ type: "main_agent_tool_call", step: 3, toolName: "writeFile" })).toBe(
"Running writeFile…",
);
});

test("maps every main_agent_progress step", () => {
expect(describeEvent({ type: "main_agent_progress", step: "toolCall", toolCall: "readFile" })).toBe(
"Calling readFile…",
);
expect(describeEvent({ type: "main_agent_progress", step: "llm_completed" })).toBe(
"Model responded.",
);
expect(describeEvent({ type: "main_agent_progress", step: "llm_failed" })).toBe(
"Model call failed.",
);
});

test("falls back to 'a tool' when a toolCall step carries no tool name", () => {
expect(describeEvent({ type: "main_agent_progress", step: "toolCall" })).toBe(
"Calling a tool…",
);
});

test("prefers a subagent's own summary over the generic working message", () => {
expect(
describeEvent({ type: "subagent_progress", agent: "coder", subagentSummary: "Wrote App.tsx" }),
).toBe("Wrote App.tsx");
expect(describeEvent({ type: "subagent_progress", agent: "coder" })).toBe("coder is working…");
});

test("describes subagent completion with its summary", () => {
expect(
describeEvent({ type: "subagent_completed", agent: "tester", taskId: 2, summary: "all green" }),
).toBe("tester finished: all green");
});

test("falls back to 'Working…' for events with no user-facing copy", () => {
const events: OrchestratorEvent[] = [
{ type: "clarification_needed", questions: [] },
{ type: "select_design", designs: [] },
{ type: "run_failed", error: "boom" },
];

for (const event of events) {
expect(describeEvent(event)).toBe("Working…");
}
});
});
Loading