Update paths in sync.sh, master/main.go, and CLAUDE.md to reflect the directory rename. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
81 lines
3.0 KiB
TypeScript
81 lines
3.0 KiB
TypeScript
// Tests for auth/password.ts
|
|
// Pure unit tests - no database needed
|
|
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { hashPassword, verifyPassword } from "./password";
|
|
|
|
describe("password", () => {
|
|
describe("hashPassword", () => {
|
|
it("returns a scrypt formatted hash", async () => {
|
|
const hash = await hashPassword("testpassword");
|
|
assert.ok(hash.startsWith("$scrypt$"));
|
|
});
|
|
|
|
it("includes all scrypt parameters", async () => {
|
|
const hash = await hashPassword("testpassword");
|
|
const parts = hash.split("$");
|
|
// Format: $scrypt$N$r$p$salt$hash
|
|
assert.equal(parts.length, 7);
|
|
assert.equal(parts[0], "");
|
|
assert.equal(parts[1], "scrypt");
|
|
// N, r, p should be numbers
|
|
assert.ok(!Number.isNaN(parseInt(parts[2], 10)));
|
|
assert.ok(!Number.isNaN(parseInt(parts[3], 10)));
|
|
assert.ok(!Number.isNaN(parseInt(parts[4], 10)));
|
|
});
|
|
|
|
it("generates different hashes for same password (different salt)", async () => {
|
|
const hash1 = await hashPassword("testpassword");
|
|
const hash2 = await hashPassword("testpassword");
|
|
assert.notEqual(hash1, hash2);
|
|
});
|
|
});
|
|
|
|
describe("verifyPassword", () => {
|
|
it("returns true for correct password", async () => {
|
|
const hash = await hashPassword("correctpassword");
|
|
const result = await verifyPassword("correctpassword", hash);
|
|
assert.equal(result, true);
|
|
});
|
|
|
|
it("returns false for incorrect password", async () => {
|
|
const hash = await hashPassword("correctpassword");
|
|
const result = await verifyPassword("wrongpassword", hash);
|
|
assert.equal(result, false);
|
|
});
|
|
|
|
it("throws for invalid hash format", async () => {
|
|
await assert.rejects(
|
|
verifyPassword("password", "invalid-hash"),
|
|
/Invalid password hash format/,
|
|
);
|
|
});
|
|
|
|
it("throws for non-scrypt hash", async () => {
|
|
await assert.rejects(
|
|
verifyPassword("password", "$bcrypt$10$salt$hash"),
|
|
/Invalid password hash format/,
|
|
);
|
|
});
|
|
|
|
it("works with empty password", async () => {
|
|
const hash = await hashPassword("");
|
|
const result = await verifyPassword("", hash);
|
|
assert.equal(result, true);
|
|
});
|
|
|
|
it("works with unicode password", async () => {
|
|
const hash = await hashPassword("p@$$w0rd\u{1F511}");
|
|
const result = await verifyPassword("p@$$w0rd\u{1F511}", hash);
|
|
assert.equal(result, true);
|
|
});
|
|
|
|
it("is case sensitive", async () => {
|
|
const hash = await hashPassword("Password");
|
|
const result = await verifyPassword("password", hash);
|
|
assert.equal(result, false);
|
|
});
|
|
});
|
|
});
|