forked from syntaxbullet/AuroraBot-discord
refactor: initial moves
This commit is contained in:
291
shared/modules/moderation/moderation.service.test.ts
Normal file
291
shared/modules/moderation/moderation.service.test.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
|
||||
import { describe, it, expect, mock, beforeEach } from "bun:test";
|
||||
import { ModerationService } from "@shared/modules/moderation/moderation.service";
|
||||
import { moderationCases } from "@db/schema";
|
||||
import { CaseType } from "@shared/lib/constants";
|
||||
|
||||
// Mock Drizzle Functions
|
||||
const mockFindFirst = mock();
|
||||
const mockFindMany = mock();
|
||||
const mockInsert = mock();
|
||||
const mockUpdate = mock();
|
||||
const mockValues = mock();
|
||||
const mockReturning = mock();
|
||||
const mockSet = mock();
|
||||
const mockWhere = mock();
|
||||
|
||||
// Mock Config
|
||||
const mockConfig = {
|
||||
moderation: {
|
||||
cases: {
|
||||
dmOnWarn: true,
|
||||
autoTimeoutThreshold: 3
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mock.module("@/lib/config", () => ({
|
||||
config: mockConfig
|
||||
}));
|
||||
|
||||
// Mock View
|
||||
const mockGetUserWarningEmbed = mock(() => ({}));
|
||||
mock.module("./moderation.view", () => ({
|
||||
getUserWarningEmbed: mockGetUserWarningEmbed
|
||||
}));
|
||||
|
||||
// Mock DrizzleClient
|
||||
mock.module("@shared/db/DrizzleClient", () => ({
|
||||
DrizzleClient: {
|
||||
query: {
|
||||
moderationCases: {
|
||||
findFirst: mockFindFirst,
|
||||
findMany: mockFindMany,
|
||||
},
|
||||
},
|
||||
insert: mockInsert,
|
||||
update: mockUpdate,
|
||||
}
|
||||
}));
|
||||
|
||||
// Setup chains
|
||||
mockInsert.mockReturnValue({ values: mockValues });
|
||||
mockValues.mockReturnValue({ returning: mockReturning });
|
||||
mockUpdate.mockReturnValue({ set: mockSet });
|
||||
mockSet.mockReturnValue({ where: mockWhere });
|
||||
mockWhere.mockReturnValue({ returning: mockReturning });
|
||||
|
||||
describe("ModerationService", () => {
|
||||
beforeEach(() => {
|
||||
mockFindFirst.mockReset();
|
||||
mockFindMany.mockReset();
|
||||
mockInsert.mockClear();
|
||||
mockUpdate.mockClear();
|
||||
mockValues.mockClear();
|
||||
mockReturning.mockClear();
|
||||
mockSet.mockClear();
|
||||
mockWhere.mockClear();
|
||||
mockGetUserWarningEmbed.mockClear();
|
||||
// Reset config to defaults
|
||||
mockConfig.moderation.cases.dmOnWarn = true;
|
||||
mockConfig.moderation.cases.autoTimeoutThreshold = 3;
|
||||
});
|
||||
|
||||
describe("issueWarning", () => {
|
||||
const defaultOptions = {
|
||||
userId: "123456789",
|
||||
username: "testuser",
|
||||
moderatorId: "987654321",
|
||||
moderatorName: "mod",
|
||||
reason: "test reason",
|
||||
guildName: "Test Guild"
|
||||
};
|
||||
|
||||
it("should issue a warning and attempt to DM the user", async () => {
|
||||
mockFindFirst.mockResolvedValue({ caseId: "CASE-0001" });
|
||||
mockReturning.mockResolvedValue([{ caseId: "CASE-0002" }]);
|
||||
mockFindMany.mockResolvedValue([{ type: CaseType.WARN, active: true }]); // 1 warning total
|
||||
|
||||
const mockDmTarget = { send: mock() };
|
||||
|
||||
const result = await ModerationService.issueWarning({
|
||||
...defaultOptions,
|
||||
dmTarget: mockDmTarget
|
||||
});
|
||||
|
||||
expect(result.moderationCase).toBeDefined();
|
||||
expect(result.warningCount).toBe(1);
|
||||
expect(mockDmTarget.send).toHaveBeenCalled();
|
||||
expect(mockGetUserWarningEmbed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not DM if dmOnWarn is false", async () => {
|
||||
mockConfig.moderation.cases.dmOnWarn = false;
|
||||
mockFindFirst.mockResolvedValue({ caseId: "CASE-0001" });
|
||||
mockReturning.mockResolvedValue([{ caseId: "CASE-0002" }]);
|
||||
mockFindMany.mockResolvedValue([]);
|
||||
|
||||
const mockDmTarget = { send: mock() };
|
||||
|
||||
await ModerationService.issueWarning({
|
||||
...defaultOptions,
|
||||
dmTarget: mockDmTarget
|
||||
});
|
||||
|
||||
expect(mockDmTarget.send).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should trigger auto-timeout when threshold is reached", async () => {
|
||||
mockFindFirst.mockResolvedValue({ caseId: "CASE-0001" });
|
||||
mockReturning.mockResolvedValue([{ caseId: "CASE-0002" }]);
|
||||
// Simulate 3 warnings (threshold is 3)
|
||||
mockFindMany.mockResolvedValue([{}, {}, {}]);
|
||||
|
||||
const mockTimeoutTarget = { timeout: mock() };
|
||||
|
||||
const result = await ModerationService.issueWarning({
|
||||
...defaultOptions,
|
||||
timeoutTarget: mockTimeoutTarget
|
||||
});
|
||||
|
||||
expect(result.autoTimeoutIssued).toBe(true);
|
||||
expect(mockTimeoutTarget.timeout).toHaveBeenCalledWith(86400000, expect.stringContaining("3 warnings"));
|
||||
// Should create two cases: one for warn, one for timeout
|
||||
expect(mockInsert).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("should not timeout if threshold is not reached", async () => {
|
||||
mockFindFirst.mockResolvedValue({ caseId: "CASE-0001" });
|
||||
mockReturning.mockResolvedValue([{ caseId: "CASE-0002" }]);
|
||||
// Simulate 2 warnings (threshold is 3)
|
||||
mockFindMany.mockResolvedValue([{}, {}]);
|
||||
|
||||
const mockTimeoutTarget = { timeout: mock() };
|
||||
|
||||
const result = await ModerationService.issueWarning({
|
||||
...defaultOptions,
|
||||
timeoutTarget: mockTimeoutTarget
|
||||
});
|
||||
|
||||
expect(result.autoTimeoutIssued).toBe(false);
|
||||
expect(mockTimeoutTarget.timeout).not.toHaveBeenCalled();
|
||||
expect(mockInsert).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getNextCaseId", () => {
|
||||
it("should return CASE-0001 if no cases exist", async () => {
|
||||
mockFindFirst.mockResolvedValue(undefined);
|
||||
// Accessing private method via bracket notation for testing
|
||||
const nextId = await (ModerationService as any).getNextCaseId();
|
||||
expect(nextId).toBe("CASE-0001");
|
||||
});
|
||||
|
||||
it("should increment the latest case ID", async () => {
|
||||
mockFindFirst.mockResolvedValue({ caseId: "CASE-0042" });
|
||||
const nextId = await (ModerationService as any).getNextCaseId();
|
||||
expect(nextId).toBe("CASE-0043");
|
||||
});
|
||||
|
||||
it("should handle padding correctly (e.g., 9 -> 0010)", async () => {
|
||||
mockFindFirst.mockResolvedValue({ caseId: "CASE-0009" });
|
||||
const nextId = await (ModerationService as any).getNextCaseId();
|
||||
expect(nextId).toBe("CASE-0010");
|
||||
});
|
||||
});
|
||||
|
||||
describe("createCase", () => {
|
||||
it("should create a new moderation case with correct values", async () => {
|
||||
mockFindFirst.mockResolvedValue({ caseId: "CASE-0001" });
|
||||
const mockNewCase = {
|
||||
caseId: "CASE-0002",
|
||||
type: CaseType.WARN,
|
||||
userId: 123456789n,
|
||||
username: "testuser",
|
||||
moderatorId: 987654321n,
|
||||
moderatorName: "mod",
|
||||
reason: "test reason",
|
||||
metadata: {},
|
||||
active: true
|
||||
};
|
||||
mockReturning.mockResolvedValue([mockNewCase]);
|
||||
|
||||
const result = await ModerationService.createCase({
|
||||
type: CaseType.WARN,
|
||||
userId: "123456789",
|
||||
username: "testuser",
|
||||
moderatorId: "987654321",
|
||||
moderatorName: "mod",
|
||||
reason: "test reason"
|
||||
});
|
||||
|
||||
expect(result?.caseId).toBe("CASE-0002");
|
||||
expect(mockInsert).toHaveBeenCalled();
|
||||
expect(mockValues).toHaveBeenCalledWith(expect.objectContaining({
|
||||
caseId: "CASE-0002",
|
||||
type: CaseType.WARN,
|
||||
userId: 123456789n,
|
||||
reason: "test reason"
|
||||
}));
|
||||
});
|
||||
|
||||
it("should set active to false for non-warn types", async () => {
|
||||
mockFindFirst.mockResolvedValue(undefined);
|
||||
mockReturning.mockImplementation((values) => [values]); // Simplified mock
|
||||
|
||||
const result = await ModerationService.createCase({
|
||||
type: CaseType.BAN,
|
||||
userId: "123456789",
|
||||
username: "testuser",
|
||||
moderatorId: "987654321",
|
||||
moderatorName: "mod",
|
||||
reason: "test reason"
|
||||
});
|
||||
|
||||
expect(mockValues).toHaveBeenCalledWith(expect.objectContaining({
|
||||
active: false
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCaseById", () => {
|
||||
it("should return a case by its ID", async () => {
|
||||
const mockCase = { caseId: "CASE-0001", reason: "test" };
|
||||
mockFindFirst.mockResolvedValue(mockCase);
|
||||
|
||||
const result = await ModerationService.getCaseById("CASE-0001");
|
||||
expect(result).toEqual(mockCase as any);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserCases", () => {
|
||||
it("should return all cases for a user", async () => {
|
||||
const mockCases = [{ caseId: "CASE-0001" }, { caseId: "CASE-0002" }];
|
||||
mockFindMany.mockResolvedValue(mockCases);
|
||||
|
||||
const result = await ModerationService.getUserCases("123456789");
|
||||
expect(result).toHaveLength(2);
|
||||
expect(mockFindMany).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearCase", () => {
|
||||
it("should update a case to be inactive and resolved", async () => {
|
||||
const mockUpdatedCase = { caseId: "CASE-0001", active: false };
|
||||
mockReturning.mockResolvedValue([mockUpdatedCase]);
|
||||
|
||||
const result = await ModerationService.clearCase({
|
||||
caseId: "CASE-0001",
|
||||
clearedBy: "987654321",
|
||||
clearedByName: "mod",
|
||||
reason: "resolved"
|
||||
});
|
||||
|
||||
expect(result?.active).toBe(false);
|
||||
expect(mockUpdate).toHaveBeenCalled();
|
||||
expect(mockSet).toHaveBeenCalledWith(expect.objectContaining({
|
||||
active: false,
|
||||
resolvedBy: 987654321n,
|
||||
resolvedReason: "resolved"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getActiveWarningCount", () => {
|
||||
it("should return the number of active warnings", async () => {
|
||||
mockFindMany.mockResolvedValue([
|
||||
{ id: 1n, type: CaseType.WARN, active: true },
|
||||
{ id: 2n, type: CaseType.WARN, active: true }
|
||||
]);
|
||||
|
||||
const count = await ModerationService.getActiveWarningCount("123456789");
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
|
||||
it("should return 0 if no active warnings", async () => {
|
||||
mockFindMany.mockResolvedValue([]);
|
||||
const count = await ModerationService.getActiveWarningCount("123456789");
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user