import { expect } from 'chai'; import { describe, it } from 'mocha'; import { Validator, ValidationError, LazyValidator } from '../src/validation/Validator'; import { decodeJWT } from '../src/util/TokenUtil'; // Helper function to create mock JWT tokens const createMockJWT = (payload: any): string => { const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString('base64'); const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64'); const signature = "mock-signature"; return `${header}.${encodedPayload}.${signature}`; }; describe('decodeJWT', () => { it('should decode a valid JWT token with Bearer prefix', () => { const payload = { sub: "1234567890", name: "John Doe", iat: 1516239022 }; const token = createMockJWT(payload); const authHeader = `Bearer ${token}`; const result = decodeJWT(authHeader); expect(result).to.deep.equal(payload); }); it('should decode JWT generated by GCP for a Service Account (e.g. PubSub)', () => { const authHeader = "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg4NDg5MjEyMmUyOTM5ZmQxZjMxMzc1YjJiMzYzZWM4MTU3MjNiYmIiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJ0b3RvLTg5MGQ4czkwYSIsImF6cCI6IjExNDYwMTcwNjIyMjQyODYwNDMwMyIsImVtYWlsIjoidG90by1wdWJzdWJAdG90b2V4cGVyaW1lbnRzLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTc2MTgxMjQ5MSwiaWF0IjoxNzYxODA4ODkxLCJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTQ2MDE3MDYyMjI0Mjg2MDQzMDMifQ.WAcnNo6Ec1OvUceBtUu_1snRSnY8Zqsp2elpjbdEOx7Ue21lI4fRuPjZna7XcbXT1ZzXyJZUkJlIIpDSjaxntIM9bBJJ2vGBVw8ZRSDq7JemWLBnyO3-ecTVtVfG8cpjRLJ3VYfZdPlX8taXt4zlvn6oVlQcLQ02CVb_ZolGUQTJ_zGjxChnBHg6zLwIDkxmCDz8r1IYdPPn0A8BvAgnkXQslMjlRS614vh7d1udc4ARL8Ix9txpQT5rSD3gpa-UhoVA1TAp26LUn1tSKsKOlpQF8IX4LsFLcSCFTPnf-W4ZVkl_79OX0ZwEsJ6Gsm78vMqQeR_ayRX77Gxa4oyXpA"; const result = decodeJWT(authHeader); expect(result.iss).to.equal("https://accounts.google.com"); expect(result.email).to.equal("toto-pubsub@totoexperiments.iam.gserviceaccount.com"); expect(result.aud).to.equal("toto-890d8s90a"); expect(result.exp).to.equal(1761812491); expect(result.iat).to.equal(1761808891); }); it('should decode JWT token with authProvider field', () => { const payload = { sub: "user123", authProvider: "custom", email: "test@example.com" }; const token = createMockJWT(payload); const authHeader = `Bearer ${token}`; const result = decodeJWT(authHeader); expect(result.authProvider).to.equal("custom"); expect(result.email).to.equal("test@example.com"); }); it('should decode JWT token with Google issuer', () => { const payload = { iss: "accounts.google.com", sub: "google-user-id", email: "user@gmail.com" }; const token = createMockJWT(payload); const authHeader = `Bearer ${token}`; const result = decodeJWT(authHeader); expect(result.iss).to.equal("accounts.google.com"); expect(result.email).to.equal("user@gmail.com"); }); it('should throw error for malformed JWT token', () => { const authHeader = "Bearer invalid.token"; expect(() => decodeJWT(authHeader)).to.throw(); }); it('should throw error for JWT with invalid base64 payload', () => { const authHeader = "Bearer header.invalid-base64.signature"; expect(() => decodeJWT(authHeader)).to.throw(); }); it('should throw error for JWT with missing parts', () => { const authHeader = "Bearer onlyonepart"; expect(() => decodeJWT(authHeader)).to.throw(); }); // Note: This test reveals a bug in the logic - should use && instead of || it('should handle the current logic bug with null/undefined check', () => { const authHeader = "Bearer "; // Due to the OR condition bug, this will always try to decode expect(() => decodeJWT(authHeader)).to.throw(); }); }); describe('Validator', () => { let validator: Validator; beforeEach(() => { validator = new LazyValidator(); }); describe('ValidationError', () => { it('should create ValidationError with code and message', () => { const error = new ValidationError(400, "Test error"); expect(error.code).to.equal(400); expect(error.message).to.equal("Test error"); expect(error.subcode).to.be.undefined; }); it('should create ValidationError with subcode', () => { const error = new ValidationError(412, "Version error", "app-version-not-compatible"); expect(error.code).to.equal(412); expect(error.message).to.equal("Version error"); expect(error.subcode).to.equal("app-version-not-compatible"); }); }); describe('LazyValidator', () => { it('should create a LazyValidator instance', () => { const lazyValidator = new LazyValidator(); expect(lazyValidator).to.be.instanceOf(Validator); expect(lazyValidator.props).to.deep.equal({}); }); }); });