summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorJustZvan <justzvan@justzvan.xyz>2026-02-06 12:16:40 +0100
committerJustZvan <justzvan@justzvan.xyz>2026-02-06 12:16:40 +0100
commite904e9634548e47d611bdcbb88d7b180b927fd5f (patch)
tree21aa5be08fc5b22585508c0263ee5ea4effcc593 /test
feat: initial commit!
Diffstat (limited to 'test')
-rw-r--r--test/auth.test.ts114
-rw-r--r--test/database.test.ts101
-rw-r--r--test/parent-routes.test.ts131
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);
+ });
+});