d7b374f8ab
New features: - ActivityController and manager for CW sales activities (CRUD) - ForecastProductController for opportunity forecast/product lines - CW member cache with dual-layer (in-memory + Redis) resolution - Catalog category/subcategory/ecosystem taxonomy module - Quote statuses type definitions with CW mapping - User-defined fields (UDF) module with cache and event refresh - Company sites CW module with serialization - Procurement manager filters (category, ecosystem, manufacturer, price, stock) - Opportunity notes CRUD and product line management via CW API - Opportunity type definitions endpoint Updates: - OpportunityController: CW refresh, company hydration, activities, custom fields - UserController: cwIdentifier field for CW member linking - CatalogItemController: category/subcategory fields from CW - PermissionNodes: sales note/product CRUD nodes, subCategories, collectPermissions - API routes: procurement categories/filters, sales notes/products, opportunity types - Global events: UDF and member refresh intervals on startup Tests (414 passing): - ActivityController, ForecastProductController, OpportunityController unit tests - UserController cwIdentifier tests - catalogCategories, companySites, memberCache, procurement module tests - activityTypes, opportunityTypes, quoteStatuses type tests - permissionNodes subCategories and getAllPermissionNodes tests - Updated test setup with redis mock, API method mocks, and builder helpers
102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
|
|
|
// The memberCache module depends on constants (prisma + redis) which are mocked
|
|
// in setup.ts. We can import the functions and test their pure-logic paths.
|
|
|
|
import {
|
|
resolveMemberName,
|
|
setMemberCache,
|
|
getMemberCache,
|
|
resolveMember,
|
|
} from "../../src/modules/cw-utils/members/memberCache";
|
|
import { Collection } from "@discordjs/collection";
|
|
import type { CWMember } from "../../src/modules/cw-utils/members/fetchAllMembers";
|
|
|
|
function buildTestMember(overrides: Partial<CWMember> = {}): CWMember {
|
|
return {
|
|
id: 10,
|
|
identifier: "jroberts",
|
|
firstName: "John",
|
|
lastName: "Roberts",
|
|
officeEmail: "john@test.com",
|
|
inactiveFlag: false,
|
|
_info: {},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("memberCache", () => {
|
|
beforeEach(async () => {
|
|
// Reset cache to empty before each test
|
|
await setMemberCache(new Collection<string, CWMember>());
|
|
});
|
|
|
|
describe("setMemberCache / getMemberCache", () => {
|
|
test("stores and retrieves members", async () => {
|
|
const members = new Collection<string, CWMember>();
|
|
members.set("jroberts", buildTestMember());
|
|
members.set("asmith", buildTestMember({ id: 20, identifier: "asmith", firstName: "Alice", lastName: "Smith" }));
|
|
|
|
await setMemberCache(members);
|
|
const cached = await getMemberCache();
|
|
|
|
expect(cached.size).toBe(2);
|
|
expect(cached.get("jroberts")?.firstName).toBe("John");
|
|
expect(cached.get("asmith")?.firstName).toBe("Alice");
|
|
});
|
|
|
|
test("empty cache returns empty collection", async () => {
|
|
const cached = await getMemberCache();
|
|
// May be empty or hydrated from redis mock (which returns null)
|
|
expect(cached.size).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("resolveMemberName", () => {
|
|
test("returns full name when member exists", async () => {
|
|
const members = new Collection<string, CWMember>();
|
|
members.set("jroberts", buildTestMember());
|
|
await setMemberCache(members);
|
|
|
|
expect(resolveMemberName("jroberts")).toBe("John Roberts");
|
|
});
|
|
|
|
test("returns raw identifier when member not found", () => {
|
|
expect(resolveMemberName("unknown-user")).toBe("unknown-user");
|
|
});
|
|
|
|
test("falls back to identifier if name parts are empty", async () => {
|
|
const members = new Collection<string, CWMember>();
|
|
members.set("empty", buildTestMember({ identifier: "empty", firstName: "", lastName: "" }));
|
|
await setMemberCache(members);
|
|
|
|
expect(resolveMemberName("empty")).toBe("empty");
|
|
});
|
|
});
|
|
|
|
describe("resolveMember", () => {
|
|
test("returns resolved member with local user id null when no local user", async () => {
|
|
const members = new Collection<string, CWMember>();
|
|
members.set("jroberts", buildTestMember());
|
|
await setMemberCache(members);
|
|
|
|
const resolved = await resolveMember("jroberts");
|
|
|
|
expect(resolved.identifier).toBe("jroberts");
|
|
expect(resolved.name).toBe("John Roberts");
|
|
expect(resolved.cwMemberId).toBe(10);
|
|
// prisma.user.findFirst is mocked to return null
|
|
expect(resolved.id).toBeNull();
|
|
});
|
|
|
|
test("returns fallback values when member not in cache", async () => {
|
|
const resolved = await resolveMember("unknown");
|
|
|
|
expect(resolved.identifier).toBe("unknown");
|
|
expect(resolved.name).toBe("unknown");
|
|
expect(resolved.cwMemberId).toBeNull();
|
|
expect(resolved.id).toBeNull();
|
|
});
|
|
});
|
|
});
|