From 08e7ec5e32515a3b534c10bcff1b0f910543c18e Mon Sep 17 00:00:00 2001 From: Rakshak05 <159248180+Rakshak05@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:48:21 +0530 Subject: [PATCH] Resolves issue-#6648 --- ...-protection.username-normalization.test.ts | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 services/security/track-user-protection.username-normalization.test.ts diff --git a/services/security/track-user-protection.username-normalization.test.ts b/services/security/track-user-protection.username-normalization.test.ts new file mode 100644 index 000000000..90292be2b --- /dev/null +++ b/services/security/track-user-protection.username-normalization.test.ts @@ -0,0 +1,124 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { trackUserProtection, TrackUserProtection } from './track-user-protection'; +import { gitHubUserValidator } from '../github/validate-user'; + +vi.mock('../github/validate-user', () => ({ + gitHubUserValidator: { + validateUser: vi.fn(), + }, +})); + +describe('TrackUserProtection Username Normalization', () => { + beforeEach(() => { + trackUserProtection.reset(); + vi.clearAllMocks(); + vi.mocked(gitHubUserValidator.validateUser).mockResolvedValue(true); + }); + + describe('Leading and trailing whitespace handling', () => { + it('ignores leading and trailing whitespace in validateFormat()', () => { + expect(trackUserProtection.validateFormat(' octocat ')).toBe(true); + expect(trackUserProtection.validateFormat('\toctocat\n')).toBe(true); + expect(trackUserProtection.validateFormat(' ')).toBe(false); + }); + + it('ignores leading and trailing whitespace in isWriteAllowed() and recordWrite()', () => { + trackUserProtection.recordWrite(' octocat '); + + expect(trackUserProtection.isWriteAllowed('octocat')).toBe(false); + expect(trackUserProtection.isWriteAllowed(' octocat')).toBe(false); + expect(trackUserProtection.isWriteAllowed('octocat ')).toBe(false); + expect(trackUserProtection.isWriteAllowed(' octocat ')).toBe(false); + }); + + it('ignores leading and trailing whitespace in verifyAndDeduplicate()', async () => { + trackUserProtection.recordWrite(' octocat '); + + const result = await trackUserProtection.verifyAndDeduplicate(' octocat '); + expect(result.allowed).toBe(false); + expect(result.reason).toBe('COOLDOWN_ACTIVE'); + }); + }); + + describe('Case-insensitivity across APIs', () => { + it('treats usernames case-insensitively in validateFormat()', () => { + expect(trackUserProtection.validateFormat('OctoCat')).toBe(true); + expect(trackUserProtection.validateFormat('OCTOCAT')).toBe(true); + expect(trackUserProtection.validateFormat('octocat')).toBe(true); + }); + + it('treats usernames case-insensitively across recordWrite() and isWriteAllowed()', () => { + trackUserProtection.recordWrite('OctoCat'); + + expect(trackUserProtection.isWriteAllowed('octocat')).toBe(false); + expect(trackUserProtection.isWriteAllowed('OCTOCAT')).toBe(false); + expect(trackUserProtection.isWriteAllowed('OctoCat')).toBe(false); + }); + + it('treats usernames case-insensitively in verifyAndDeduplicate()', async () => { + trackUserProtection.recordWrite('OctoCat'); + + const result = await trackUserProtection.verifyAndDeduplicate('OCTOCAT'); + expect(result.allowed).toBe(false); + expect(result.reason).toBe('COOLDOWN_ACTIVE'); + }); + }); + + describe('Consistent behavior across all public APIs', () => { + it('behaves consistently when combining casing and whitespace variations', async () => { + expect(trackUserProtection.validateFormat(' OctoCat ')).toBe(true); + + trackUserProtection.recordWrite(' OctoCat '); + + expect(trackUserProtection.isWriteAllowed('octocat')).toBe(false); + expect(trackUserProtection.isWriteAllowed(' OCTOCAT ')).toBe(false); + + const verifyResult = await trackUserProtection.verifyAndDeduplicate('\tOCTOCAT\n'); + expect(verifyResult.allowed).toBe(false); + expect(verifyResult.reason).toBe('COOLDOWN_ACTIVE'); + }); + + it('allows writes for new normalized usernames while blocking recorded variations', async () => { + trackUserProtection.recordWrite(' OctoCat '); + + expect(trackUserProtection.isWriteAllowed('different-user')).toBe(true); + + const allowedResult = await trackUserProtection.verifyAndDeduplicate(' Different-User '); + expect(allowedResult.allowed).toBe(true); + expect(gitHubUserValidator.validateUser).toHaveBeenCalledWith(' Different-User '); + }); + }); + + describe('Single internal entry referencing for equivalent usernames', () => { + it('references the same internal entry for OctoCat, octocat, and OCTOCAT', () => { + trackUserProtection.recordWrite('OctoCat'); + expect(trackUserProtection.isWriteAllowed('octocat')).toBe(false); + + // Overwriting with equivalent username variation + trackUserProtection.recordWrite(' octocat '); + expect(trackUserProtection.isWriteAllowed('OCTOCAT')).toBe(false); + + trackUserProtection.recordWrite('OCTOCAT'); + expect(trackUserProtection.isWriteAllowed('OctoCat')).toBe(false); + }); + + it('clears internal entries for all equivalent variations on reset()', () => { + trackUserProtection.recordWrite(' OctoCat '); + expect(trackUserProtection.isWriteAllowed('OCTOCAT')).toBe(false); + + trackUserProtection.reset(); + + expect(trackUserProtection.isWriteAllowed('octocat')).toBe(true); + expect(trackUserProtection.isWriteAllowed('OCTOCAT')).toBe(true); + expect(trackUserProtection.isWriteAllowed(' OctoCat ')).toBe(true); + }); + + it('works correctly with direct instance from getInstance()', () => { + const instance = TrackUserProtection.getInstance(); + instance.recordWrite(' TestUser '); + + expect(instance.isWriteAllowed('testuser')).toBe(false); + expect(instance.isWriteAllowed('TESTUSER')).toBe(false); + }); + }); +});