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
24 changes: 24 additions & 0 deletions packages/database/crypto.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { hashPassword, verifyPassword } from "./crypto";

describe("password hashing", () => {
it("verifies the original password and rejects a different one", async () => {
const hash = await hashPassword("correct horse battery staple");

expect(hash).not.toBe("correct horse battery staple");
expect(await verifyPassword(hash, "correct horse battery staple")).toBe(
true,
);
expect(await verifyPassword(hash, "wrong password")).toBe(false);
});

it("rejects empty hash and password inputs", async () => {
const hash = await hashPassword("non-empty password");

expect(await verifyPassword("", "non-empty password")).toBe(false);
expect(await verifyPassword(hash, "")).toBe(false);
await expect(hashPassword("")).rejects.toThrow(
"Cannot hash empty or null password",
);
});
});
6 changes: 4 additions & 2 deletions packages/database/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"db:studio": "drizzle-kit studio --config=drizzle.config.ts",
"db:check-integrity": "node scripts/check-migration-integrity.js",
"drizzle-kit": "pnpm dotenv -e ../../.env drizzle-kit --config=drizzle.config.ts",
"build": "tsdown"
"build": "tsdown",
"test": "vitest run"
},
"dependencies": {
"@cap/env": "workspace:*",
Expand Down Expand Up @@ -50,7 +51,8 @@
"react-dom": "^19.1.1",
"react-router-dom": "^6.18.0",
"tsconfig": "workspace:*",
"typescript": "^5.8.3"
"typescript": "^5.8.3",
"vitest": "~2.1.9"
},
"engines": {
"node": ">=20"
Expand Down
8 changes: 8 additions & 0 deletions packages/database/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
environment: "node",
include: ["**/*.test.ts"],
},
});
38 changes: 38 additions & 0 deletions packages/env/build.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { afterEach, describe, expect, it, vi } from "vitest";

const originalEnv = { ...process.env };

function resetEnv() {
for (const key of Object.keys(process.env)) {
delete process.env[key];
}
Object.assign(process.env, originalEnv);
}

afterEach(() => {
resetEnv();
vi.resetModules();
});

describe("buildEnv", () => {
it("uses WEB_URL as the public web URL fallback", async () => {
process.env.WEB_URL = "https://cap.example";
delete process.env.NEXT_PUBLIC_WEB_URL;

const { buildEnv } = await import("./build");

expect(buildEnv.NEXT_PUBLIC_WEB_URL).toBe("https://cap.example");
});

it("caches the parsed environment after first access", async () => {
process.env.WEB_URL = "https://first.example";

const { buildEnv } = await import("./build");

expect(buildEnv.NEXT_PUBLIC_WEB_URL).toBe("https://first.example");

process.env.WEB_URL = "https://second.example";

expect(buildEnv.NEXT_PUBLIC_WEB_URL).toBe("https://first.example");
});
});
6 changes: 4 additions & 2 deletions packages/env/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "./index.ts",
"types": "./index.ts",
"scripts": {
"build": "tsdown"
"build": "tsdown",
"test": "vitest run"
},
"publishConfig": {
"main": "./dist/index.js"
Expand All @@ -15,6 +16,7 @@
"zod": "^3.25.76"
},
"devDependencies": {
"@types/node": "^22.15.14"
"@types/node": "^22.15.14",
"vitest": "~2.1.9"
}
}
50 changes: 50 additions & 0 deletions packages/env/server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { afterEach, describe, expect, it, vi } from "vitest";

const originalEnv = { ...process.env };

function resetEnv() {
for (const key of Object.keys(process.env)) {
delete process.env[key];
}
Object.assign(process.env, originalEnv);
}

function setRequiredServerEnv() {
process.env.DATABASE_URL = "mysql://user:password@localhost:3306/cap";
process.env.WEB_URL = "https://cap.example";
process.env.NEXTAUTH_SECRET = "test-secret";
process.env.NEXTAUTH_URL = "https://cap.example";
process.env.CAP_AWS_BUCKET = "cap-test-bucket";
process.env.CAP_AWS_REGION = "us-east-1";
process.env.NODE_ENV = "test";
delete process.env.S3_PATH_STYLE;
delete process.env.CAP_VIDEOS_DEFAULT_PUBLIC;
}
Comment on lines +12 to +22
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The "parses boolean string defaults" test is brittle because it checks that S3_PATH_STYLE and CAP_VIDEOS_DEFAULT_PUBLIC take their schema-level defaults (true), yet setRequiredServerEnv() never unsets those variables. originalEnv is captured at module-load time, so if the developer's or CI environment already has either var set (e.g. S3_PATH_STYLE=false), ...process.env in experimental__runtimeEnv will propagate that value and override the Zod default, causing the test to fail with no obvious cause.

Suggested change
function setRequiredServerEnv() {
process.env.DATABASE_URL = "mysql://user:password@localhost:3306/cap";
process.env.WEB_URL = "https://cap.example";
process.env.NEXTAUTH_SECRET = "test-secret";
process.env.NEXTAUTH_URL = "https://cap.example";
process.env.CAP_AWS_BUCKET = "cap-test-bucket";
process.env.CAP_AWS_REGION = "us-east-1";
process.env.NODE_ENV = "test";
}
function setRequiredServerEnv() {
process.env.DATABASE_URL = "mysql://user:password@localhost:3306/cap";
process.env.WEB_URL = "https://cap.example";
process.env.NEXTAUTH_SECRET = "test-secret";
process.env.NEXTAUTH_URL = "https://cap.example";
process.env.CAP_AWS_BUCKET = "cap-test-bucket";
process.env.CAP_AWS_REGION = "us-east-1";
process.env.NODE_ENV = "test";
delete process.env.S3_PATH_STYLE;
delete process.env.CAP_VIDEOS_DEFAULT_PUBLIC;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/env/server.test.ts
Line: 12-20

Comment:
The "parses boolean string defaults" test is brittle because it checks that `S3_PATH_STYLE` and `CAP_VIDEOS_DEFAULT_PUBLIC` take their schema-level defaults (`true`), yet `setRequiredServerEnv()` never unsets those variables. `originalEnv` is captured at module-load time, so if the developer's or CI environment already has either var set (e.g. `S3_PATH_STYLE=false`), `...process.env` in `experimental__runtimeEnv` will propagate that value and override the Zod default, causing the test to fail with no obvious cause.

```suggestion
function setRequiredServerEnv() {
	process.env.DATABASE_URL = "mysql://user:password@localhost:3306/cap";
	process.env.WEB_URL = "https://cap.example";
	process.env.NEXTAUTH_SECRET = "test-secret";
	process.env.NEXTAUTH_URL = "https://cap.example";
	process.env.CAP_AWS_BUCKET = "cap-test-bucket";
	process.env.CAP_AWS_REGION = "us-east-1";
	process.env.NODE_ENV = "test";
	delete process.env.S3_PATH_STYLE;
	delete process.env.CAP_VIDEOS_DEFAULT_PUBLIC;
}
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

@sypham98-prog sypham98-prog May 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f8c9fcc. setRequiredServerEnv now deletes S3_PATH_STYLE and CAP_VIDEOS_DEFAULT_PUBLIC before importing serverEnv, so the boolean default assertions are isolated from any ambient developer or CI environment. Verification passed: corepack pnpm --filter @cap/env test; corepack pnpm --filter @cap/database test; corepack pnpm exec biome check on the touched test config and package files.


afterEach(() => {
resetEnv();
vi.resetModules();
});

describe("serverEnv", () => {
it("maps CAP_AWS_ENDPOINT to both S3 endpoint aliases", async () => {
setRequiredServerEnv();
process.env.CAP_AWS_ENDPOINT = "https://s3.example";

const { serverEnv } = await import("./server");

const env = serverEnv();
expect(env.S3_PUBLIC_ENDPOINT).toBe("https://s3.example");
expect(env.S3_INTERNAL_ENDPOINT).toBe("https://s3.example");
});

it("parses boolean string defaults for server settings", async () => {
setRequiredServerEnv();

const { serverEnv } = await import("./server");

const env = serverEnv();
expect(env.S3_PATH_STYLE).toBe(true);
expect(env.CAP_VIDEOS_DEFAULT_PUBLIC).toBe(true);
});
});
8 changes: 8 additions & 0 deletions packages/env/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
environment: "node",
include: ["**/*.test.ts"],
},
});
150 changes: 150 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.