fix: replace Math.random() IDs with crypto.randomUUID() across all services
- billing-service: licenses, subscriptions (pay_, lic_) - growth-service: invitations, referrals (inv_, ref_) - platform-service: auth, audit (usr_, aud_) - tracker-service: items, comments, votes, public (trk_, cmt_, vote_) - Add votes.test.ts — closes the only missing module test
This commit is contained in:
parent
d39c447c52
commit
86a56339ab
@ -31,7 +31,7 @@ export async function licenseRoutes(app: FastifyInstance) {
|
|||||||
const key = repo.generateKey();
|
const key = repo.generateKey();
|
||||||
|
|
||||||
const doc: LicenseDoc = {
|
const doc: LicenseDoc = {
|
||||||
id: `lic_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `lic_${crypto.randomUUID()}`,
|
||||||
productId: PRODUCT_ID,
|
productId: PRODUCT_ID,
|
||||||
key,
|
key,
|
||||||
userId: input.userId,
|
userId: input.userId,
|
||||||
|
|||||||
@ -96,7 +96,7 @@ export async function subscriptionRoutes(app: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
const input = parsed.data;
|
const input = parsed.data;
|
||||||
const doc: PaymentDoc = {
|
const doc: PaymentDoc = {
|
||||||
id: `pay_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `pay_${crypto.randomUUID()}`,
|
||||||
productId: PRODUCT_ID,
|
productId: PRODUCT_ID,
|
||||||
...input,
|
...input,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export async function invitationRoutes(app: FastifyInstance) {
|
|||||||
const input = parsed.data;
|
const input = parsed.data;
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const doc: InvitationCodeDoc = {
|
const doc: InvitationCodeDoc = {
|
||||||
id: `inv_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `inv_${crypto.randomUUID()}`,
|
||||||
productId: PRODUCT_ID,
|
productId: PRODUCT_ID,
|
||||||
code: input.code.toUpperCase().replace(/[^A-Z0-9-]/g, ""),
|
code: input.code.toUpperCase().replace(/[^A-Z0-9-]/g, ""),
|
||||||
description: input.description,
|
description: input.description,
|
||||||
@ -126,7 +126,7 @@ export async function invitationRoutes(app: FastifyInstance) {
|
|||||||
const input = parsed.data;
|
const input = parsed.data;
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const doc: InvitationCodeDoc = {
|
const doc: InvitationCodeDoc = {
|
||||||
id: `inv_${Date.now()}_${i}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `inv_${crypto.randomUUID()}`,
|
||||||
productId: PRODUCT_ID,
|
productId: PRODUCT_ID,
|
||||||
code: input.code.toUpperCase().replace(/[^A-Z0-9-]/g, ""),
|
code: input.code.toUpperCase().replace(/[^A-Z0-9-]/g, ""),
|
||||||
description: input.description,
|
description: input.description,
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export async function referralRoutes(app: FastifyInstance) {
|
|||||||
|
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const doc: ReferralDoc = {
|
const doc: ReferralDoc = {
|
||||||
id: `ref_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `ref_${crypto.randomUUID()}`,
|
||||||
productId: PRODUCT_ID,
|
productId: PRODUCT_ID,
|
||||||
referrerId: input.referrerId,
|
referrerId: input.referrerId,
|
||||||
referrerEmail: input.referrerEmail,
|
referrerEmail: input.referrerEmail,
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export async function auditRoutes(app: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
const input = parsed.data;
|
const input = parsed.data;
|
||||||
const doc: AuditDoc = {
|
const doc: AuditDoc = {
|
||||||
id: `aud_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `aud_${crypto.randomUUID()}`,
|
||||||
productId: PRODUCT_ID,
|
productId: PRODUCT_ID,
|
||||||
...input,
|
...input,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export async function authRoutes(app: FastifyInstance) {
|
|||||||
|
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const user: UserDoc = {
|
const user: UserDoc = {
|
||||||
id: `usr_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `usr_${crypto.randomUUID()}`,
|
||||||
productId: PRODUCT_ID,
|
productId: PRODUCT_ID,
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
passwordHash: await repo.hashPassword(password),
|
passwordHash: await repo.hashPassword(password),
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export async function commentRoutes(app: FastifyInstance) {
|
|||||||
|
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const doc: CommentDoc = {
|
const doc: CommentDoc = {
|
||||||
id: `cmt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `cmt_${crypto.randomUUID()}`,
|
||||||
itemId,
|
itemId,
|
||||||
productId: item.productId,
|
productId: item.productId,
|
||||||
authorId: auth.sub,
|
authorId: auth.sub,
|
||||||
|
|||||||
@ -93,7 +93,7 @@ export async function itemRoutes(app: FastifyInstance) {
|
|||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
const doc: TrackerItemDoc = {
|
const doc: TrackerItemDoc = {
|
||||||
id: `trk_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `trk_${crypto.randomUUID()}`,
|
||||||
productId: pid,
|
productId: pid,
|
||||||
type: input.type,
|
type: input.type,
|
||||||
status: "open",
|
status: "open",
|
||||||
|
|||||||
@ -99,7 +99,7 @@ export async function publicRoutes(app: FastifyInstance) {
|
|||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
const doc: TrackerItemDoc = {
|
const doc: TrackerItemDoc = {
|
||||||
id: `trk_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `trk_${crypto.randomUUID()}`,
|
||||||
productId: pid,
|
productId: pid,
|
||||||
type: input.type,
|
type: input.type,
|
||||||
status: "open",
|
status: "open",
|
||||||
@ -123,7 +123,7 @@ export async function publicRoutes(app: FastifyInstance) {
|
|||||||
|
|
||||||
// Auto-vote for the submitter
|
// Auto-vote for the submitter
|
||||||
await voteRepo.create({
|
await voteRepo.create({
|
||||||
id: `vote_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `vote_${crypto.randomUUID()}`,
|
||||||
itemId: created.id,
|
itemId: created.id,
|
||||||
productId: pid,
|
productId: pid,
|
||||||
userId: `email:${input.email}`,
|
userId: `email:${input.email}`,
|
||||||
@ -157,7 +157,7 @@ export async function publicRoutes(app: FastifyInstance) {
|
|||||||
return { voted: false, voteCount: newCount };
|
return { voted: false, voteCount: newCount };
|
||||||
} else {
|
} else {
|
||||||
await voteRepo.create({
|
await voteRepo.create({
|
||||||
id: `vote_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `vote_${crypto.randomUUID()}`,
|
||||||
itemId: id,
|
itemId: id,
|
||||||
productId: item.productId,
|
productId: item.productId,
|
||||||
userId: voterId,
|
userId: voterId,
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export async function voteRoutes(app: FastifyInstance) {
|
|||||||
} else {
|
} else {
|
||||||
// Add vote
|
// Add vote
|
||||||
const doc: VoteDoc = {
|
const doc: VoteDoc = {
|
||||||
id: `vote_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
id: `vote_${crypto.randomUUID()}`,
|
||||||
itemId,
|
itemId,
|
||||||
productId: item.productId,
|
productId: item.productId,
|
||||||
userId: auth.sub,
|
userId: auth.sub,
|
||||||
|
|||||||
58
services/tracker-service/src/modules/votes/votes.test.ts
Normal file
58
services/tracker-service/src/modules/votes/votes.test.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Votes module unit tests — validates VoteDoc shape and route exports.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import type { VoteDoc } from "./types.js";
|
||||||
|
|
||||||
|
describe("VoteDoc", () => {
|
||||||
|
it("accepts a valid vote document", () => {
|
||||||
|
const doc: VoteDoc = {
|
||||||
|
id: `vote_${crypto.randomUUID()}`,
|
||||||
|
itemId: "trk_abc123",
|
||||||
|
productId: "lysnrai",
|
||||||
|
userId: "usr_xyz",
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(doc.id).toMatch(/^vote_/);
|
||||||
|
expect(doc.itemId).toBe("trk_abc123");
|
||||||
|
expect(doc.productId).toBe("lysnrai");
|
||||||
|
expect(doc.userId).toBe("usr_xyz");
|
||||||
|
expect(doc.createdAt).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("generates unique IDs with crypto.randomUUID", () => {
|
||||||
|
const ids = new Set(
|
||||||
|
Array.from({ length: 100 }, () => `vote_${crypto.randomUUID()}`)
|
||||||
|
);
|
||||||
|
expect(ids.size).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("id format uses UUID (no Math.random)", () => {
|
||||||
|
const id = `vote_${crypto.randomUUID()}`;
|
||||||
|
// UUID v4 pattern after prefix
|
||||||
|
expect(id).toMatch(
|
||||||
|
/^vote_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("createdAt is ISO 8601 format", () => {
|
||||||
|
const doc: VoteDoc = {
|
||||||
|
id: `vote_${crypto.randomUUID()}`,
|
||||||
|
itemId: "trk_test",
|
||||||
|
productId: "lysnrai",
|
||||||
|
userId: "usr_test",
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
// ISO 8601 pattern
|
||||||
|
expect(doc.createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("voteRoutes export", () => {
|
||||||
|
it("exports voteRoutes function", async () => {
|
||||||
|
const mod = await import("./routes.js");
|
||||||
|
expect(typeof mod.voteRoutes).toBe("function");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user