NAC_Blockchain/services/nac-admin/server/nac-admin.test.ts

227 lines
8.5 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from "vitest";
// ─── Mock environment variables (no hardcoded values) ───────────────────────
vi.mock("./secrets", () => ({
getSecrets: () => ({
nacMysqlUrl: "mysql://test:test@localhost:3306/nac_id",
nacMongoUrl: "mongodb://test:test@localhost:27017",
nacJwtSecret: "test-jwt-secret-at-least-32-chars-long",
nacAdminToken: "test-admin-token",
}),
}));
vi.mock("./nacAuth", () => ({
loginWithNacCredentials: vi.fn(),
verifyNacToken: vi.fn(),
listNacUsers: vi.fn(),
getNacUserCount: vi.fn(),
}));
vi.mock("./mongodb", () => ({
getMongoDb: vi.fn(),
}));
import { getSecrets } from "./secrets";
import { loginWithNacCredentials, verifyNacToken } from "./nacAuth";
import { getMongoDb } from "./mongodb";
// ─── Test: Secrets Module ────────────────────────────────────────────────────
describe("secrets module", () => {
it("returns all required secrets from environment variables", () => {
const secrets = getSecrets();
expect(secrets).toHaveProperty("nacMysqlUrl");
expect(secrets).toHaveProperty("nacMongoUrl");
expect(secrets).toHaveProperty("nacJwtSecret");
expect(secrets).toHaveProperty("nacAdminToken");
});
it("all secret values are non-empty strings", () => {
const secrets = getSecrets();
Object.values(secrets).forEach((v) => {
expect(typeof v).toBe("string");
expect(v.length).toBeGreaterThan(0);
});
});
it("secrets do not contain hardcoded production values", () => {
const secrets = getSecrets();
// Ensure no real IP addresses or production credentials appear in test
const secretStr = JSON.stringify(secrets);
expect(secretStr).not.toContain("103.96.148.7");
expect(secretStr).not.toContain("idP0ZaRGyLsTUA3a");
expect(secretStr).not.toContain("XKUigTFMJXhH");
});
});
// ─── Test: NAC Authentication ────────────────────────────────────────────────
describe("nacAuth module", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("loginWithNacCredentials returns null for invalid credentials", async () => {
(loginWithNacCredentials as any).mockResolvedValue(null);
const result = await loginWithNacCredentials("wrong@example.com", "wrongpassword");
expect(result).toBeNull();
});
it("loginWithNacCredentials returns user and token for valid credentials", async () => {
const mockResult = {
token: "jwt.token.here",
user: {
id: 1,
email: "admin@newassetchain.io",
name: "Admin",
role: "admin",
kyc_level: 3,
},
};
(loginWithNacCredentials as any).mockResolvedValue(mockResult);
const result = await loginWithNacCredentials("admin@newassetchain.io", "password123");
expect(result).not.toBeNull();
expect(result?.token).toBeTruthy();
expect(result?.user.email).toBe("admin@newassetchain.io");
expect(result?.user.role).toBe("admin");
});
it("verifyNacToken returns null for invalid token", async () => {
(verifyNacToken as any).mockResolvedValue(null);
const result = await verifyNacToken("invalid.token");
expect(result).toBeNull();
});
it("verifyNacToken returns user payload for valid token", async () => {
const mockUser = { id: 1, email: "admin@newassetchain.io", role: "admin" };
(verifyNacToken as any).mockResolvedValue(mockUser);
const result = await verifyNacToken("valid.jwt.token");
expect(result).toEqual(mockUser);
});
});
// ─── Test: MongoDB Connection ────────────────────────────────────────────────
describe("mongodb module", () => {
it("getMongoDb returns null when connection fails", async () => {
(getMongoDb as any).mockResolvedValue(null);
const db = await getMongoDb();
expect(db).toBeNull();
});
it("getMongoDb returns db instance when connected", async () => {
const mockDb = {
collection: vi.fn().mockReturnValue({
find: vi.fn().mockReturnValue({ toArray: vi.fn().mockResolvedValue([]) }),
countDocuments: vi.fn().mockResolvedValue(0),
insertOne: vi.fn().mockResolvedValue({ insertedId: "test-id" }),
}),
};
(getMongoDb as any).mockResolvedValue(mockDb);
const db = await getMongoDb();
expect(db).not.toBeNull();
expect(db).toHaveProperty("collection");
});
});
// ─── Test: RBAC Role Validation ──────────────────────────────────────────────
describe("RBAC role validation", () => {
const VALID_ROLES = ["admin", "reviewer", "legal", "viewer"];
it("all defined roles are valid", () => {
VALID_ROLES.forEach((role) => {
expect(["admin", "reviewer", "legal", "viewer"]).toContain(role);
});
});
it("admin role has highest privilege", () => {
const roleHierarchy = { admin: 4, reviewer: 3, legal: 2, viewer: 1 };
expect(roleHierarchy["admin"]).toBeGreaterThan(roleHierarchy["reviewer"]);
expect(roleHierarchy["reviewer"]).toBeGreaterThan(roleHierarchy["legal"]);
expect(roleHierarchy["legal"]).toBeGreaterThan(roleHierarchy["viewer"]);
});
it("invalid role is rejected", () => {
const isValidRole = (role: string) => VALID_ROLES.includes(role);
expect(isValidRole("superuser")).toBe(false);
expect(isValidRole("root")).toBe(false);
expect(isValidRole("")).toBe(false);
});
});
// ─── Test: Knowledge Base Data Validation ────────────────────────────────────
describe("knowledge base data validation", () => {
const VALID_JURISDICTIONS = ["CN", "HK", "US", "EU", "SG", "AE", "ALL"];
const VALID_ASSET_TYPES = ["RealEstate", "Securities", "DigitalToken", "Commodity", "IP", "ALL"];
it("all supported jurisdictions are valid", () => {
VALID_JURISDICTIONS.forEach((j) => {
expect(j).toBeTruthy();
expect(j.length).toBeGreaterThan(0);
});
});
it("compliance rule requires jurisdiction and asset type", () => {
const validateRule = (rule: any) => {
return (
VALID_JURISDICTIONS.includes(rule.jurisdiction) &&
VALID_ASSET_TYPES.includes(rule.assetType) &&
typeof rule.ruleName === "string" &&
rule.ruleName.length > 0
);
};
const validRule = { jurisdiction: "CN", assetType: "RealEstate", ruleName: "不动产登记证要求" };
const invalidRule = { jurisdiction: "XX", assetType: "Unknown", ruleName: "" };
expect(validateRule(validRule)).toBe(true);
expect(validateRule(invalidRule)).toBe(false);
});
it("tag sequence must be an array of non-empty strings", () => {
const validateTags = (tags: any) => {
return Array.isArray(tags) && tags.length > 0 && tags.every((t: any) => typeof t === "string" && t.length > 0);
};
expect(validateTags(["CN", "RealEstate", "Required"])).toBe(true);
expect(validateTags([])).toBe(false);
expect(validateTags(["CN", "", "Required"])).toBe(false);
expect(validateTags("not-an-array")).toBe(false);
});
});
// ─── Test: Audit Log Structure ────────────────────────────────────────────────
describe("audit log structure", () => {
const VALID_ACTIONS = [
"LOGIN", "LOGOUT", "CREATE_RULE", "UPDATE_RULE", "DELETE_RULE",
"REVIEW_CASE", "CORRECT_TAG", "CREATE_TAG_RULE",
"REGISTER_PROTOCOL", "TOGGLE_PROTOCOL", "UPDATE_PROTOCOL_VERSION",
"TRIGGER_CRAWLER", "UPDATE_CRAWLER_CONFIG", "MANAGE_USER",
];
it("all audit actions are defined", () => {
expect(VALID_ACTIONS.length).toBeGreaterThan(0);
VALID_ACTIONS.forEach((action) => {
expect(typeof action).toBe("string");
expect(action.length).toBeGreaterThan(0);
});
});
it("audit log entry has required fields", () => {
const validateAuditEntry = (entry: any) => {
return (
VALID_ACTIONS.includes(entry.action) &&
typeof entry.userId === "number" &&
typeof entry.userEmail === "string" &&
entry.userEmail.includes("@") &&
entry.timestamp instanceof Date
);
};
const validEntry = {
action: "LOGIN",
userId: 1,
userEmail: "admin@newassetchain.io",
timestamp: new Date(),
};
expect(validateAuditEntry(validEntry)).toBe(true);
});
});