/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ import {describe, expect, it, vi} from 'vitest'; import {findInvite, findInvites} from './InviteUtils'; vi.mock('~/Config', () => ({ Config: { hosts: { invite: 'fluxer.gg', gift: 'fluxer.gift', marketing: 'marketing.fluxer.app', unfurlIgnored: [], }, endpoints: { webApp: 'https://web.fluxer.app', }, }, })); describe('InviteUtils', () => { describe('findInvites', () => { it('should return empty array for null or empty content', () => { expect(findInvites(null)).toEqual([]); expect(findInvites('')).toEqual([]); expect(findInvites(' ')).toEqual([]); }); it('should find invite codes from fluxer.gg URLs (direct, no /invite/)', () => { const content = 'Check out this guild: https://fluxer.gg/abc123'; const result = findInvites(content); expect(result).toEqual(['abc123']); }); it('should find invite codes from web.fluxer.app/invite/ URLs', () => { const content = 'Join us: https://web.fluxer.app/invite/test123'; const result = findInvites(content); expect(result).toHaveLength(1); expect(result[0]).toBe('test123'); }); it('should NOT match fluxer.gg/invite/ URLs', () => { const content = 'Invalid: https://fluxer.gg/invite/shouldnotwork'; const result = findInvites(content); expect(result).toEqual([]); }); it('should handle URLs without protocol', () => { const content = 'Join us: fluxer.gg/test123'; const result = findInvites(content); expect(result).toHaveLength(1); expect(result[0]).toBe('test123'); }); it('should handle URLs with hash fragment', () => { const content = 'Come join: https://web.fluxer.app/#/invite/hash456'; const result = findInvites(content); expect(result).toHaveLength(1); expect(result[0]).toBe('hash456'); }); it('should find multiple unique invite codes from different hosts', () => { const content = ` First: https://fluxer.gg/invite1 Second: https://web.fluxer.app/invite/invite2 Third: https://web.fluxer.app/#/invite/invite3 `; const result = findInvites(content); expect(result).toHaveLength(3); expect(result).toEqual(['invite1', 'invite2', 'invite3']); }); it('should deduplicate identical invite codes', () => { const content = ` https://fluxer.gg/duplicate fluxer.gg/duplicate Another mention: https://fluxer.gg/duplicate `; const result = findInvites(content); expect(result).toHaveLength(1); expect(result[0]).toBe('duplicate'); }); it('should deduplicate codes across different hosts', () => { const content = ` https://fluxer.gg/samecode https://web.fluxer.app/invite/samecode `; const result = findInvites(content); expect(result).toHaveLength(1); expect(result[0]).toBe('samecode'); }); it('should limit to maximum 10 invites', () => { let content = ''; for (let i = 1; i <= 15; i++) { content += `https://fluxer.gg/code${i.toString().padStart(2, '0')} `; } const result = findInvites(content); expect(result).toHaveLength(10); }); it('should handle invite codes with valid characters', () => { const validCodes = ['abc123', 'TEST-CODE', 'mix3d-Ch4rs', 'AB', 'a'.repeat(32)]; validCodes.forEach((code) => { const content = `https://fluxer.gg/${code}`; const result = findInvites(content); expect(result).toHaveLength(1); expect(result[0]).toBe(code); }); }); it('should ignore invite codes that are too short', () => { const code = 'a'; const content = `https://fluxer.gg/${code}`; const result = findInvites(content); expect(result).toHaveLength(0); }); it('should ignore invite codes that are too long', () => { const code = 'a'.repeat(33); const content = `https://fluxer.gg/${code}`; const result = findInvites(content); expect(result).toHaveLength(0); }); it('should handle mixed case URLs', () => { const content = 'Join: https://fluxer.gg/MixedCase123'; const result = findInvites(content); expect(result).toHaveLength(1); expect(result[0]).toBe('MixedCase123'); }); it('should handle URLs with extra text around them', () => { const content = 'Before text https://fluxer.gg/surrounded123 after text'; const result = findInvites(content); expect(result).toHaveLength(1); expect(result[0]).toBe('surrounded123'); }); it('should handle web.fluxer.app URLs with and without protocol', () => { const content = ` https://web.fluxer.app/invite/withprotocol web.fluxer.app/invite/withoutprotocol `; const result = findInvites(content); expect(result).toHaveLength(2); expect(result).toEqual(['withprotocol', 'withoutprotocol']); }); it('should handle mixed fluxer.gg and web.fluxer.app URLs', () => { const content = ` Direct: fluxer.gg/direct123 Web app: web.fluxer.app/invite/local456 Another direct: https://fluxer.gg/direct789 `; const result = findInvites(content); expect(result).toHaveLength(3); expect(result).toEqual(['direct123', 'local456', 'direct789']); }); it('should handle canary domain', () => { const content = ` Canary: https://web.canary.fluxer.app/invite/canary123 Stable: https://web.fluxer.app/invite/stable456 `; const result = findInvites(content); expect(result).toHaveLength(1); expect(result).toEqual(['stable456']); }); it('should NOT match marketing site invite URLs', () => { const content = 'Invalid: https://fluxer.app/invite/shouldnotwork'; const result = findInvites(content); expect(result).toEqual([]); }); }); describe('findInvite', () => { it('should return null for null or empty content', () => { expect(findInvite(null)).toBeNull(); expect(findInvite('')).toBeNull(); expect(findInvite(' ')).toBeNull(); }); it('should find first invite code from fluxer.gg', () => { const content = 'Check out: https://fluxer.gg/first123'; const result = findInvite(content); expect(result).toBe('first123'); }); it('should find first invite code from web.fluxer.app', () => { const content = 'Check out: https://web.fluxer.app/invite/first123'; const result = findInvite(content); expect(result).toBe('first123'); }); it('should return first invite when multiple exist', () => { const content = ` First: https://fluxer.gg/first456 Second: web.fluxer.app/invite/second789 `; const result = findInvite(content); expect(result).toBe('first456'); }); it('should handle URLs without protocol', () => { const content = 'Join: fluxer.gg/noprotocol'; const result = findInvite(content); expect(result).toBe('noprotocol'); }); it('should handle URLs with hash fragment', () => { const content = 'Visit: https://web.fluxer.app/#/invite/hashcode'; const result = findInvite(content); expect(result).toBe('hashcode'); }); it('should return null when no valid invite found', () => { const invalidContents = [ 'No invites here', 'https://other-site.com/invite/code123', 'https://fluxer.gg/invite/shouldnotmatch', 'https://fluxer.gg/a', 'https://fluxer.app/invite/marketing', ]; invalidContents.forEach((content) => { expect(findInvite(content)).toBeNull(); }); }); it('should handle case insensitive matching', () => { const content = 'Visit: HTTPS://FLUXER.GG/CaseTest'; const result = findInvite(content); expect(result).toBe('CaseTest'); }); it('should handle complex content with multiple URLs', () => { const content = ` Visit our website at https://fluxer.app Join our guild: https://fluxer.gg/complex123 Learn more at https://fluxer.app/about `; const result = findInvite(content); expect(result).toBe('complex123'); }); }); describe('edge cases', () => { it('should handle content with special regex characters', () => { const content = 'Check this (important): https://fluxer.gg/special123 [link]'; const result = findInvites(content); expect(result).toHaveLength(1); expect(result[0]).toBe('special123'); }); it('should handle very long content without crashing', () => { const longContent = `${'a'.repeat(10000)}https://fluxer.gg/buried123${'b'.repeat(10000)}`; const result = findInvites(longContent); expect(result).toEqual([]); }); it('should handle malformed URLs gracefully', () => { const content = ` https://fluxer.gg/good123 https://fluxer.gg/ https://fluxer.gg fluxer.gg/another456 web.fluxer.app/invite/valid789 `; const result = findInvites(content); expect(result).toHaveLength(3); expect(result).toEqual(['good123', 'another456', 'valid789']); }); it('should reset regex state between calls', () => { const content1 = 'https://fluxer.gg/first123'; const content2 = 'https://web.fluxer.app/invite/second456'; const result1 = findInvite(content1); const result2 = findInvite(content2); expect(result1).toBe('first123'); expect(result2).toBe('second456'); }); it('should handle codes at exact length boundaries', () => { const minCode = 'ab'; const maxCode = 'a'.repeat(32); const contentMin = `https://fluxer.gg/${minCode}`; const contentMax = `https://fluxer.gg/${maxCode}`; expect(findInvite(contentMin)).toBe(minCode); expect(findInvite(contentMax)).toBe(maxCode); }); it('should distinguish between marketing and web app domains', () => { const content = ` Marketing: https://fluxer.app/invite/marketing123 Web app: https://web.fluxer.app/invite/webapp456 Shortlink: https://fluxer.gg/shortlink789 `; const result = findInvites(content); expect(result).toHaveLength(2); expect(result).toEqual(['webapp456', 'shortlink789']); }); }); });