diff options
| author | JustZvan <justzvan@justzvan.xyz> | 2026-02-06 12:16:40 +0100 |
|---|---|---|
| committer | JustZvan <justzvan@justzvan.xyz> | 2026-02-06 12:16:40 +0100 |
| commit | e904e9634548e47d611bdcbb88d7b180b927fd5f (patch) | |
| tree | 21aa5be08fc5b22585508c0263ee5ea4effcc593 /test | |
feat: initial commit!
Diffstat (limited to 'test')
| -rw-r--r-- | test/auth.test.ts | 114 | ||||
| -rw-r--r-- | test/database.test.ts | 101 | ||||
| -rw-r--r-- | test/parent-routes.test.ts | 131 |
3 files changed, 346 insertions, 0 deletions
diff --git a/test/auth.test.ts b/test/auth.test.ts new file mode 100644 index 0000000..c7d00e1 --- /dev/null +++ b/test/auth.test.ts @@ -0,0 +1,114 @@ +import { describe, test, expect, vi } from "vitest"; +import { Request, Response, NextFunction } from "express"; +import { authParent } from "../src/middleware/auth"; +import { verifyJwt } from "../src/account/jwt"; + +vi.mock("../src/account/jwt", () => ({ + verifyJwt: vi.fn(), +})); + +describe("Auth Middleware", () => { + test("should authenticate valid parent token", async () => { + vi.mocked(verifyJwt).mockResolvedValueOnce({ + id: 1, + type: "parent" as const, + }); + + const req = { + headers: { + authorization: "Bearer valid-token", + }, + } as Request; + + const res = { + status: vi.fn().mockReturnThis(), + json: vi.fn(), + } as unknown as Response; + + const next = vi.fn() as NextFunction; + + await authParent(req, res, next); + + expect(req.user).toEqual({ + id: 1, + type: "parent", + }); + expect(next).toHaveBeenCalled(); + expect(res.status).not.toHaveBeenCalled(); + }); + + test("should reject missing authorization header", async () => { + const req = { + headers: {}, + path: "/test", + } as Request; + + const res = { + status: vi.fn().mockReturnThis(), + json: vi.fn(), + } as unknown as Response; + + const next = vi.fn() as NextFunction; + + await authParent(req, res, next); + + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith({ + success: false, + reason: "Missing or invalid authorization header", + }); + expect(next).not.toHaveBeenCalled(); + }); + + test("should reject invalid token format", async () => { + const req = { + headers: { + authorization: "InvalidFormat token", + }, + path: "/test", + } as Request; + + const res = { + status: vi.fn().mockReturnThis(), + json: vi.fn(), + } as unknown as Response; + + const next = vi.fn() as NextFunction; + + await authParent(req, res, next); + + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith({ + success: false, + reason: "Missing or invalid authorization header", + }); + expect(next).not.toHaveBeenCalled(); + }); + + test("should reject invalid JWT", async () => { + vi.mocked(verifyJwt).mockRejectedValueOnce(new Error("Invalid token")); + + const req = { + headers: { + authorization: "Bearer invalid-token", + }, + path: "/test", + } as Request; + + const res = { + status: vi.fn().mockReturnThis(), + json: vi.fn(), + } as unknown as Response; + + const next = vi.fn() as NextFunction; + + await authParent(req, res, next); + + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith({ + success: false, + reason: "Invalid or expired token", + }); + expect(next).not.toHaveBeenCalled(); + }); +}); diff --git a/test/database.test.ts b/test/database.test.ts new file mode 100644 index 0000000..d0a76c7 --- /dev/null +++ b/test/database.test.ts @@ -0,0 +1,101 @@ +import { describe, test, expect } from "vitest"; +import { db } from "../src/db/db"; +import { users, linkedDevices, deviceConfig, alerts } from "../src/db/schema"; +import { eq } from "drizzle-orm"; + +describe("Database Operations", () => { + test("should retrieve test user from database", async () => { + const testUsers = await db + .select() + .from(users) + .where(eq(users.email, "test@example.com")); + + expect(testUsers).toHaveLength(1); + expect(testUsers[0]?.email).toBe("test@example.com"); + expect(testUsers[0]?.emailVerified).toBe(true); + }); + + test("should retrieve test device from database", async () => { + const devices = await db.select().from(linkedDevices); + + expect(devices).toHaveLength(1); + expect(devices[0]?.nickname).toBe("Test Device"); + expect(devices[0]?.parentId).toBe(1); + }); + + test("should create new user", async () => { + const newUser = await db + .insert(users) + .values({ + email: "newuser@example.com", + password: "hashedpassword", + emailVerified: false, + emailCode: "654321", + pushTokens: ["token1", "token2"], + }) + .returning(); + + expect(newUser).toHaveLength(1); + expect(newUser[0]?.email).toBe("newuser@example.com"); + expect(newUser[0]?.pushTokens).toEqual(["token1", "token2"]); + }); + + test("should create device config", async () => { + const config = await db + .insert(deviceConfig) + .values({ + deviceId: 1, + disableBuddy: false, + blockAdultSites: true, + familyLinkAntiCircumvention: true, + newContactAlerts: true, + blockStrangers: false, + notifyDangerousMessages: true, + notifyNewContactAdded: true, + }) + .returning(); + + expect(config).toHaveLength(1); + expect(config[0]?.deviceId).toBe(1); + expect(config[0]?.familyLinkAntiCircumvention).toBe(true); + }); + + test("should create alert", async () => { + const alert = await db + .insert(alerts) + .values({ + deviceId: 1, + parentId: 1, + category: "test", + title: "Test Alert", + message: "This is a test alert", + summary: "Test alert summary", + confidence: 95, + packageName: "com.test.app", + timestamp: Math.floor(Date.now() / 1000), + read: false, + }) + .returning(); + + expect(alert).toHaveLength(1); + expect(alert[0]?.title).toBe("Test Alert"); + expect(alert[0]?.confidence).toBe(95); + }); + + test("should update device last online", async () => { + const newTimestamp = Math.floor(Date.now() / 1000); + + await db + .update(linkedDevices) + .set({ lastOnline: newTimestamp }) + .where(eq(linkedDevices.id, 1)); + + const device = await db + .select() + .from(linkedDevices) + .where(eq(linkedDevices.id, 1)) + .limit(1); + + expect(device[0]?.lastOnline).toBe(newTimestamp); + }); +}); diff --git a/test/parent-routes.test.ts b/test/parent-routes.test.ts new file mode 100644 index 0000000..816bbbc --- /dev/null +++ b/test/parent-routes.test.ts @@ -0,0 +1,131 @@ +import { describe, test, expect, vi, beforeEach } from "vitest"; +import request from "supertest"; +import express, { Request, Response, NextFunction } from "express"; +import { db } from "../src/db/db"; +import { linkedDevices, deviceConfig } from "../src/db/schema"; +import { eq } from "drizzle-orm"; +import createParentRouter from "../src/routes/parent"; + +vi.mock("../src/middleware/auth", () => ({ + authParent: (req: Request, res: Response, next: NextFunction) => { + req.user = { id: 1, type: "parent" }; + next(); + }, +})); + +vi.mock("../src/notifications/push", () => ({ + isValidPushToken: vi.fn(() => true), +})); + +describe("Parent Routes", () => { + let app: express.Application; + + beforeEach(async () => { + const onlineDevices = new Map(); + + app = express(); + app.use(express.json()); + app.use("/", createParentRouter(onlineDevices)); + + await db.delete(deviceConfig).execute(); + }); + + test("should get devices for parent", async () => { + const response = await request(app).get("/parent/devices").expect(200); + + expect(response.body.success).toBe(true); + expect(Array.isArray(response.body.devices)).toBe(true); + }); + + test("should get device config", async () => { + await db.insert(deviceConfig).values({ + deviceId: 1, + disableBuddy: false, + blockAdultSites: true, + familyLinkAntiCircumvention: false, + newContactAlerts: true, + blockStrangers: false, + notifyDangerousMessages: true, + notifyNewContactAdded: true, + }); + + const response = await request(app).get("/parent/controls/1").expect(200); + + expect(response.body.success).toBe(true); + expect(Array.isArray(response.body.safetyControls)).toBe(true); + const adultSitesControl = response.body.safetyControls.find( + (c: { key: string }) => c.key === "adult_sites", + ); + expect(adultSitesControl.defaultValue).toBe(true); + }); + + test("should update device config", async () => { + await db.insert(deviceConfig).values({ + deviceId: 1, + disableBuddy: false, + blockAdultSites: true, + familyLinkAntiCircumvention: false, + newContactAlerts: true, + blockStrangers: false, + notifyDangerousMessages: true, + notifyNewContactAdded: true, + }); + + const response = await request(app) + .post("/parent/controls/1") + .send({ + key: "block_strangers", + value: true, + }) + .expect(200); + + expect(response.body.success).toBe(true); + + const config = await db + .select() + .from(deviceConfig) + .where(eq(deviceConfig.deviceId, 1)) + .limit(1); + + expect(config[0]?.blockStrangers).toBe(true); + }); + + test("should rename device", async () => { + const response = await request(app) + .post("/parent/device/1/rename") + .send({ + name: "Updated Device Name", + }) + .expect(200); + + expect(response.body.success).toBe(true); + + const device = await db + .select() + .from(linkedDevices) + .where(eq(linkedDevices.id, 1)) + .limit(1); + + expect(device[0]?.nickname).toBe("Updated Device Name"); + }); + + test("should validate request body for config update", async () => { + const response = await request(app) + .post("/parent/controls/1") + .send({ + key: "invalid_key", + value: true, + }) + .expect(400); + + expect(response.body.success).toBe(false); + }); + + test("should validate device ID parameter", async () => { + const response = await request(app) + .get("/parent/controls/invalid") + .expect(400); + + expect(response.body.success).toBe(false); + }); +}); |