361 lines
15 KiB
TypeScript
361 lines
15 KiB
TypeScript
import { beforeEach, describe, expect, it } from 'vitest';
|
|
import { isGroupAllowed, isGroupUserAllowed, resolveGroupAllowedUsers, resolveGroupMode, resolveReceiveBotMessages, resolveDailyLimits, checkDailyLimit, resetDailyLimitCounters, type GroupsConfig } from './group-mode.js';
|
|
|
|
describe('group-mode helpers', () => {
|
|
describe('isGroupAllowed', () => {
|
|
it('rejects when groups config is missing (no config = no groups)', () => {
|
|
expect(isGroupAllowed(undefined, ['group-1'])).toBe(false);
|
|
});
|
|
|
|
it('rejects when groups config is empty (explicit empty allowlist)', () => {
|
|
expect(isGroupAllowed({}, ['group-1'])).toBe(false);
|
|
});
|
|
|
|
it('allows via wildcard', () => {
|
|
const groups: GroupsConfig = { '*': { mode: 'mention-only' } };
|
|
expect(isGroupAllowed(groups, ['group-1'])).toBe(true);
|
|
});
|
|
|
|
it('allows when any provided key matches', () => {
|
|
const groups: GroupsConfig = { 'server-1': { mode: 'open' } };
|
|
expect(isGroupAllowed(groups, ['chat-1', 'server-1'])).toBe(true);
|
|
});
|
|
|
|
it('rejects when no keys match and no wildcard', () => {
|
|
const groups: GroupsConfig = { 'group-2': { mode: 'open' } };
|
|
expect(isGroupAllowed(groups, ['group-1'])).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('resolveGroupMode', () => {
|
|
it('returns fallback when groups config is missing', () => {
|
|
expect(resolveGroupMode(undefined, ['group-1'], 'open')).toBe('open');
|
|
});
|
|
|
|
it('uses specific key before wildcard', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'mention-only' },
|
|
'group-1': { mode: 'open' },
|
|
};
|
|
expect(resolveGroupMode(groups, ['group-1'], 'open')).toBe('open');
|
|
});
|
|
|
|
it('uses wildcard when no specific key matches', () => {
|
|
const groups: GroupsConfig = { '*': { mode: 'listen' } };
|
|
expect(resolveGroupMode(groups, ['group-1'], 'open')).toBe('listen');
|
|
});
|
|
|
|
it('resolves disabled mode', () => {
|
|
const groups: GroupsConfig = { '*': { mode: 'disabled' } };
|
|
expect(resolveGroupMode(groups, ['group-1'], 'open')).toBe('disabled');
|
|
});
|
|
|
|
it('maps legacy requireMention=true to mention-only', () => {
|
|
const groups: GroupsConfig = { 'group-1': { requireMention: true } };
|
|
expect(resolveGroupMode(groups, ['group-1'], 'open')).toBe('mention-only');
|
|
});
|
|
|
|
it('maps legacy requireMention=false to open', () => {
|
|
const groups: GroupsConfig = { 'group-1': { requireMention: false } };
|
|
expect(resolveGroupMode(groups, ['group-1'], 'mention-only')).toBe('open');
|
|
});
|
|
|
|
it('defaults to mention-only for explicit empty group entries', () => {
|
|
const groups: GroupsConfig = { 'group-1': {} };
|
|
expect(resolveGroupMode(groups, ['group-1'], 'open')).toBe('mention-only');
|
|
});
|
|
|
|
it('defaults to mention-only for wildcard empty entry', () => {
|
|
const groups: GroupsConfig = { '*': {} };
|
|
expect(resolveGroupMode(groups, ['group-1'], 'open')).toBe('mention-only');
|
|
});
|
|
|
|
it('uses first matching key in priority order', () => {
|
|
const groups: GroupsConfig = {
|
|
'chat-1': { mode: 'listen' },
|
|
'server-1': { mode: 'open' },
|
|
};
|
|
expect(resolveGroupMode(groups, ['chat-1', 'server-1'], 'mention-only')).toBe('listen');
|
|
expect(resolveGroupMode(groups, ['chat-2', 'server-1'], 'mention-only')).toBe('open');
|
|
});
|
|
});
|
|
|
|
describe('resolveGroupAllowedUsers', () => {
|
|
it('returns undefined when groups config is missing', () => {
|
|
expect(resolveGroupAllowedUsers(undefined, ['group-1'])).toBeUndefined();
|
|
});
|
|
|
|
it('returns undefined when no allowedUsers configured', () => {
|
|
const groups: GroupsConfig = { 'group-1': { mode: 'open' } };
|
|
expect(resolveGroupAllowedUsers(groups, ['group-1'])).toBeUndefined();
|
|
});
|
|
|
|
it('returns allowedUsers from specific key', () => {
|
|
const groups: GroupsConfig = {
|
|
'group-1': { mode: 'open', allowedUsers: ['user-a', 'user-b'] },
|
|
};
|
|
expect(resolveGroupAllowedUsers(groups, ['group-1'])).toEqual(['user-a', 'user-b']);
|
|
});
|
|
|
|
it('returns allowedUsers from wildcard', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'mention-only', allowedUsers: ['user-a'] },
|
|
};
|
|
expect(resolveGroupAllowedUsers(groups, ['group-1'])).toEqual(['user-a']);
|
|
});
|
|
|
|
it('prefers specific key over wildcard', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'mention-only', allowedUsers: ['wildcard-user'] },
|
|
'group-1': { mode: 'open', allowedUsers: ['specific-user'] },
|
|
};
|
|
expect(resolveGroupAllowedUsers(groups, ['group-1'])).toEqual(['specific-user']);
|
|
});
|
|
|
|
it('uses first matching key in priority order', () => {
|
|
const groups: GroupsConfig = {
|
|
'chat-1': { mode: 'open', allowedUsers: ['chat-user'] },
|
|
'server-1': { mode: 'open', allowedUsers: ['server-user'] },
|
|
};
|
|
expect(resolveGroupAllowedUsers(groups, ['chat-1', 'server-1'])).toEqual(['chat-user']);
|
|
expect(resolveGroupAllowedUsers(groups, ['chat-2', 'server-1'])).toEqual(['server-user']);
|
|
});
|
|
});
|
|
|
|
describe('resolveReceiveBotMessages', () => {
|
|
it('returns false when groups config is missing', () => {
|
|
expect(resolveReceiveBotMessages(undefined, ['group-1'])).toBe(false);
|
|
});
|
|
|
|
it('returns false when receiveBotMessages is not configured', () => {
|
|
const groups: GroupsConfig = { 'group-1': { mode: 'listen' } };
|
|
expect(resolveReceiveBotMessages(groups, ['group-1'])).toBe(false);
|
|
});
|
|
|
|
it('returns true when receiveBotMessages is enabled on specific key', () => {
|
|
const groups: GroupsConfig = {
|
|
'group-1': { mode: 'listen', receiveBotMessages: true },
|
|
};
|
|
expect(resolveReceiveBotMessages(groups, ['group-1'])).toBe(true);
|
|
});
|
|
|
|
it('returns false when receiveBotMessages is explicitly disabled', () => {
|
|
const groups: GroupsConfig = {
|
|
'group-1': { mode: 'listen', receiveBotMessages: false },
|
|
};
|
|
expect(resolveReceiveBotMessages(groups, ['group-1'])).toBe(false);
|
|
});
|
|
|
|
it('uses wildcard as fallback', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'listen', receiveBotMessages: true },
|
|
};
|
|
expect(resolveReceiveBotMessages(groups, ['group-1'])).toBe(true);
|
|
});
|
|
|
|
it('prefers specific key over wildcard', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'listen', receiveBotMessages: true },
|
|
'group-1': { mode: 'listen', receiveBotMessages: false },
|
|
};
|
|
expect(resolveReceiveBotMessages(groups, ['group-1'])).toBe(false);
|
|
});
|
|
|
|
it('uses first matching key in priority order', () => {
|
|
const groups: GroupsConfig = {
|
|
'chat-1': { mode: 'listen', receiveBotMessages: true },
|
|
'server-1': { mode: 'listen', receiveBotMessages: false },
|
|
};
|
|
expect(resolveReceiveBotMessages(groups, ['chat-1', 'server-1'])).toBe(true);
|
|
expect(resolveReceiveBotMessages(groups, ['chat-2', 'server-1'])).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isGroupUserAllowed', () => {
|
|
it('allows all users when no groups config', () => {
|
|
expect(isGroupUserAllowed(undefined, ['group-1'], 'any-user')).toBe(true);
|
|
});
|
|
|
|
it('allows all users when no allowedUsers configured', () => {
|
|
const groups: GroupsConfig = { 'group-1': { mode: 'open' } };
|
|
expect(isGroupUserAllowed(groups, ['group-1'], 'any-user')).toBe(true);
|
|
});
|
|
|
|
it('allows user in the list', () => {
|
|
const groups: GroupsConfig = {
|
|
'group-1': { mode: 'open', allowedUsers: ['user-a', 'user-b'] },
|
|
};
|
|
expect(isGroupUserAllowed(groups, ['group-1'], 'user-a')).toBe(true);
|
|
expect(isGroupUserAllowed(groups, ['group-1'], 'user-b')).toBe(true);
|
|
});
|
|
|
|
it('rejects user not in the list', () => {
|
|
const groups: GroupsConfig = {
|
|
'group-1': { mode: 'open', allowedUsers: ['user-a'] },
|
|
};
|
|
expect(isGroupUserAllowed(groups, ['group-1'], 'user-c')).toBe(false);
|
|
});
|
|
|
|
it('uses wildcard allowedUsers as fallback', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'mention-only', allowedUsers: ['owner'] },
|
|
};
|
|
expect(isGroupUserAllowed(groups, ['group-1'], 'owner')).toBe(true);
|
|
expect(isGroupUserAllowed(groups, ['group-1'], 'stranger')).toBe(false);
|
|
});
|
|
|
|
it('specific group overrides wildcard allowedUsers', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'mention-only', allowedUsers: ['owner'] },
|
|
'open-group': { mode: 'open', allowedUsers: ['guest'] },
|
|
};
|
|
// open-group has its own list
|
|
expect(isGroupUserAllowed(groups, ['open-group'], 'guest')).toBe(true);
|
|
expect(isGroupUserAllowed(groups, ['open-group'], 'owner')).toBe(false);
|
|
// other groups fall back to wildcard
|
|
expect(isGroupUserAllowed(groups, ['other-group'], 'owner')).toBe(true);
|
|
expect(isGroupUserAllowed(groups, ['other-group'], 'guest')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('resolveDailyLimits', () => {
|
|
it('returns empty when groups config is missing', () => {
|
|
expect(resolveDailyLimits(undefined, ['group-1'])).toEqual({});
|
|
});
|
|
|
|
it('returns empty when no daily limits configured', () => {
|
|
const groups: GroupsConfig = { 'group-1': { mode: 'open' } };
|
|
expect(resolveDailyLimits(groups, ['group-1'])).toEqual({});
|
|
});
|
|
|
|
it('resolves dailyLimit from specific key', () => {
|
|
const groups: GroupsConfig = {
|
|
'group-1': { mode: 'open', dailyLimit: 50 },
|
|
};
|
|
expect(resolveDailyLimits(groups, ['group-1'])).toEqual({ dailyLimit: 50, dailyUserLimit: undefined, matchedKey: 'group-1' });
|
|
});
|
|
|
|
it('resolves dailyUserLimit from specific key', () => {
|
|
const groups: GroupsConfig = {
|
|
'group-1': { mode: 'open', dailyUserLimit: 10 },
|
|
};
|
|
expect(resolveDailyLimits(groups, ['group-1'])).toEqual({ dailyLimit: undefined, dailyUserLimit: 10, matchedKey: 'group-1' });
|
|
});
|
|
|
|
it('resolves both limits together', () => {
|
|
const groups: GroupsConfig = {
|
|
'group-1': { mode: 'open', dailyLimit: 100, dailyUserLimit: 20 },
|
|
};
|
|
expect(resolveDailyLimits(groups, ['group-1'])).toEqual({ dailyLimit: 100, dailyUserLimit: 20, matchedKey: 'group-1' });
|
|
});
|
|
|
|
it('uses wildcard as fallback', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'open', dailyLimit: 30 },
|
|
};
|
|
expect(resolveDailyLimits(groups, ['group-1'])).toEqual({ dailyLimit: 30, dailyUserLimit: undefined, matchedKey: '*' });
|
|
});
|
|
|
|
it('prefers specific key over wildcard', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'open', dailyLimit: 100 },
|
|
'group-1': { mode: 'open', dailyLimit: 10 },
|
|
};
|
|
expect(resolveDailyLimits(groups, ['group-1'])).toEqual({ dailyLimit: 10, dailyUserLimit: undefined, matchedKey: 'group-1' });
|
|
});
|
|
|
|
it('uses first matching key in priority order', () => {
|
|
const groups: GroupsConfig = {
|
|
'chat-1': { mode: 'open', dailyLimit: 5 },
|
|
'server-1': { mode: 'open', dailyLimit: 50 },
|
|
};
|
|
expect(resolveDailyLimits(groups, ['chat-1', 'server-1'])).toEqual({ dailyLimit: 5, dailyUserLimit: undefined, matchedKey: 'chat-1' });
|
|
expect(resolveDailyLimits(groups, ['chat-2', 'server-1'])).toEqual({ dailyLimit: 50, dailyUserLimit: undefined, matchedKey: 'server-1' });
|
|
});
|
|
|
|
it('inherits undefined fields from wildcard', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'open', dailyUserLimit: 10 },
|
|
'channel-123': { mode: 'open', dailyLimit: 50 },
|
|
};
|
|
// channel-123 sets dailyLimit, wildcard provides dailyUserLimit
|
|
expect(resolveDailyLimits(groups, ['channel-123'])).toEqual({
|
|
dailyLimit: 50,
|
|
dailyUserLimit: 10,
|
|
matchedKey: 'channel-123',
|
|
});
|
|
});
|
|
|
|
it('specific key overrides wildcard for the same field', () => {
|
|
const groups: GroupsConfig = {
|
|
'*': { mode: 'open', dailyLimit: 100, dailyUserLimit: 20 },
|
|
'group-1': { mode: 'open', dailyLimit: 10 },
|
|
};
|
|
// group-1 overrides dailyLimit, inherits dailyUserLimit from wildcard
|
|
expect(resolveDailyLimits(groups, ['group-1'])).toEqual({
|
|
dailyLimit: 10,
|
|
dailyUserLimit: 20,
|
|
matchedKey: 'group-1',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('checkDailyLimit', () => {
|
|
beforeEach(() => {
|
|
resetDailyLimitCounters();
|
|
});
|
|
|
|
it('allows when no limits configured', () => {
|
|
const result = checkDailyLimit('test:group', 'user-1', {});
|
|
expect(result).toEqual({ allowed: true });
|
|
});
|
|
|
|
it('enforces dailyLimit (group-wide total)', () => {
|
|
const limits = { dailyLimit: 3 };
|
|
expect(checkDailyLimit('test:group', 'user-1', limits).allowed).toBe(true);
|
|
expect(checkDailyLimit('test:group', 'user-2', limits).allowed).toBe(true);
|
|
expect(checkDailyLimit('test:group', 'user-3', limits).allowed).toBe(true);
|
|
// 4th message exceeds group-wide limit regardless of user
|
|
const result = checkDailyLimit('test:group', 'user-4', limits);
|
|
expect(result).toEqual({ allowed: false, reason: 'daily-limit' });
|
|
});
|
|
|
|
it('enforces dailyUserLimit (per-user)', () => {
|
|
const limits = { dailyUserLimit: 2 };
|
|
expect(checkDailyLimit('test:group', 'user-1', limits).allowed).toBe(true);
|
|
expect(checkDailyLimit('test:group', 'user-1', limits).allowed).toBe(true);
|
|
// user-1 is blocked
|
|
const result = checkDailyLimit('test:group', 'user-1', limits);
|
|
expect(result).toEqual({ allowed: false, reason: 'daily-user-limit' });
|
|
// user-2 is still allowed
|
|
expect(checkDailyLimit('test:group', 'user-2', limits).allowed).toBe(true);
|
|
});
|
|
|
|
it('checks group limit before user limit', () => {
|
|
const limits = { dailyLimit: 2, dailyUserLimit: 5 };
|
|
expect(checkDailyLimit('test:group', 'user-1', limits).allowed).toBe(true);
|
|
expect(checkDailyLimit('test:group', 'user-2', limits).allowed).toBe(true);
|
|
// Group limit hit -- reason should be daily-limit, not daily-user-limit
|
|
const result = checkDailyLimit('test:group', 'user-3', limits);
|
|
expect(result).toEqual({ allowed: false, reason: 'daily-limit' });
|
|
});
|
|
|
|
it('isolates counters between different groups', () => {
|
|
const limits = { dailyLimit: 1 };
|
|
expect(checkDailyLimit('discord:group-a', 'user-1', limits).allowed).toBe(true);
|
|
expect(checkDailyLimit('discord:group-b', 'user-1', limits).allowed).toBe(true);
|
|
// group-a is full, group-b is full, but they're independent
|
|
expect(checkDailyLimit('discord:group-a', 'user-1', limits).allowed).toBe(false);
|
|
expect(checkDailyLimit('discord:group-b', 'user-1', limits).allowed).toBe(false);
|
|
});
|
|
|
|
it('does not increment counters when denied', () => {
|
|
const limits = { dailyLimit: 2 };
|
|
expect(checkDailyLimit('test:group', 'user-1', limits).allowed).toBe(true); // count=1
|
|
expect(checkDailyLimit('test:group', 'user-1', limits).allowed).toBe(true); // count=2
|
|
expect(checkDailyLimit('test:group', 'user-1', limits).allowed).toBe(false); // denied, count stays 2
|
|
expect(checkDailyLimit('test:group', 'user-1', limits).allowed).toBe(false); // still denied, count stays 2
|
|
});
|
|
});
|
|
});
|