| | import mongoose from 'mongoose'; |
| | import { MongoMemoryServer } from 'mongodb-memory-server'; |
| | import type * as t from '~/types'; |
| | import { createTokenMethods } from './token'; |
| | import tokenSchema from '~/schema/token'; |
| |
|
| | |
| | jest.mock('~/config/winston', () => ({ |
| | error: jest.fn(), |
| | info: jest.fn(), |
| | debug: jest.fn(), |
| | })); |
| |
|
| | let mongoServer: MongoMemoryServer; |
| | let Token: mongoose.Model<t.IToken>; |
| | let methods: ReturnType<typeof createTokenMethods>; |
| |
|
| | beforeAll(async () => { |
| | mongoServer = await MongoMemoryServer.create(); |
| | const mongoUri = mongoServer.getUri(); |
| | await mongoose.connect(mongoUri); |
| |
|
| | |
| | Token = mongoose.models.Token || mongoose.model<t.IToken>('Token', tokenSchema); |
| |
|
| | |
| | methods = createTokenMethods(mongoose); |
| | }); |
| |
|
| | afterAll(async () => { |
| | await mongoose.disconnect(); |
| | await mongoServer.stop(); |
| | }); |
| |
|
| | beforeEach(async () => { |
| | await mongoose.connection.dropDatabase(); |
| | }); |
| |
|
| | describe('Token Methods - Detailed Tests', () => { |
| | describe('createToken', () => { |
| | test('should create a token with correct expiry time', async () => { |
| | const userId = new mongoose.Types.ObjectId(); |
| | const tokenData = { |
| | token: 'test-token-123', |
| | userId: userId, |
| | email: 'test@example.com', |
| | expiresIn: 3600, |
| | }; |
| |
|
| | const token = await methods.createToken(tokenData); |
| |
|
| | expect(token).toBeDefined(); |
| | expect(token.token).toBe(tokenData.token); |
| | expect(token.userId.toString()).toBe(userId.toString()); |
| | expect(token.email).toBe(tokenData.email); |
| |
|
| | |
| | const expectedExpiry = new Date(token.createdAt.getTime() + tokenData.expiresIn * 1000); |
| | expect(token.expiresAt.getTime()).toBe(expectedExpiry.getTime()); |
| | }); |
| |
|
| | test('should create token with all required fields', async () => { |
| | const userId = new mongoose.Types.ObjectId(); |
| | const tokenData = { |
| | token: 'minimal-token', |
| | userId: userId, |
| | expiresIn: 1800, |
| | }; |
| |
|
| | const token = await methods.createToken(tokenData); |
| |
|
| | expect(token).toBeDefined(); |
| | expect(token.token).toBe(tokenData.token); |
| | expect(token.userId.toString()).toBe(userId.toString()); |
| | expect(token.email).toBeUndefined(); |
| | }); |
| |
|
| | test('should create token with identifier field', async () => { |
| | const userId = new mongoose.Types.ObjectId(); |
| | const tokenData = { |
| | token: 'identifier-token', |
| | userId: userId, |
| | identifier: 'oauth-identifier-123', |
| | expiresIn: 7200, |
| | }; |
| |
|
| | const token = await methods.createToken(tokenData); |
| |
|
| | expect(token).toBeDefined(); |
| | expect(token.identifier).toBe(tokenData.identifier); |
| | }); |
| | }); |
| |
|
| | describe('findToken', () => { |
| | let user1Id: mongoose.Types.ObjectId; |
| | let user2Id: mongoose.Types.ObjectId; |
| |
|
| | beforeEach(async () => { |
| | user1Id = new mongoose.Types.ObjectId(); |
| | user2Id = new mongoose.Types.ObjectId(); |
| |
|
| | await Token.create([ |
| | { |
| | token: 'token-1', |
| | userId: user1Id, |
| | email: 'user1@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }, |
| | { |
| | token: 'token-2', |
| | userId: user2Id, |
| | email: 'user2@example.com', |
| | identifier: 'oauth-123', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }, |
| | { |
| | token: 'token-3', |
| | userId: user1Id, |
| | email: 'user1-alt@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }, |
| | ]); |
| | }); |
| |
|
| | test('should find token by token value', async () => { |
| | const found = await methods.findToken({ token: 'token-1' }); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('token-1'); |
| | expect(found?.userId.toString()).toBe(user1Id.toString()); |
| | }); |
| |
|
| | test('should find token by userId', async () => { |
| | const found = await methods.findToken({ userId: user2Id.toString() }); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('token-2'); |
| | expect(found?.email).toBe('user2@example.com'); |
| | }); |
| |
|
| | test('should find token by email', async () => { |
| | const found = await methods.findToken({ email: 'user2@example.com' }); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('token-2'); |
| | expect(found?.userId.toString()).toBe(user2Id.toString()); |
| | }); |
| |
|
| | test('should find token by identifier', async () => { |
| | const found = await methods.findToken({ identifier: 'oauth-123' }); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('token-2'); |
| | expect(found?.identifier).toBe('oauth-123'); |
| | }); |
| |
|
| | test('should find token by multiple criteria (AND condition)', async () => { |
| | const found = await methods.findToken({ |
| | userId: user1Id.toString(), |
| | email: 'user1@example.com', |
| | }); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('token-1'); |
| | }); |
| |
|
| | test('should return null for non-existent token', async () => { |
| | const found = await methods.findToken({ token: 'non-existent' }); |
| |
|
| | expect(found).toBeNull(); |
| | }); |
| |
|
| | test('should return null when criteria do not match together', async () => { |
| | const found = await methods.findToken({ |
| | userId: user1Id.toString(), |
| | email: 'user2@example.com', |
| | }); |
| |
|
| | expect(found).toBeNull(); |
| | }); |
| |
|
| | test('should find most recent token with sort option', async () => { |
| | const recentUserId = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | const oldDate = new Date(Date.now() - 7200000); |
| | const midDate = new Date(Date.now() - 3600000); |
| | const newDate = new Date(); |
| |
|
| | await Token.create([ |
| | { |
| | token: 'old-token', |
| | userId: recentUserId, |
| | email: 'recent@example.com', |
| | createdAt: oldDate, |
| | expiresAt: new Date(oldDate.getTime() + 86400000), |
| | }, |
| | { |
| | token: 'mid-token', |
| | userId: recentUserId, |
| | email: 'recent@example.com', |
| | createdAt: midDate, |
| | expiresAt: new Date(midDate.getTime() + 86400000), |
| | }, |
| | { |
| | token: 'new-token', |
| | userId: recentUserId, |
| | email: 'recent@example.com', |
| | createdAt: newDate, |
| | expiresAt: new Date(newDate.getTime() + 86400000), |
| | }, |
| | ]); |
| |
|
| | |
| | const found = await methods.findToken( |
| | { userId: recentUserId.toString() }, |
| | { sort: { createdAt: -1 } }, |
| | ); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('new-token'); |
| | expect(found?.createdAt.getTime()).toBe(newDate.getTime()); |
| | }); |
| |
|
| | test('should find oldest token with ascending sort', async () => { |
| | const sortUserId = new mongoose.Types.ObjectId(); |
| |
|
| | const oldDate = new Date(Date.now() - 7200000); |
| | const midDate = new Date(Date.now() - 3600000); |
| | const newDate = new Date(); |
| |
|
| | await Token.create([ |
| | { |
| | token: 'sort-old', |
| | userId: sortUserId, |
| | email: 'sort@example.com', |
| | createdAt: oldDate, |
| | expiresAt: new Date(oldDate.getTime() + 86400000), |
| | }, |
| | { |
| | token: 'sort-mid', |
| | userId: sortUserId, |
| | email: 'sort@example.com', |
| | createdAt: midDate, |
| | expiresAt: new Date(midDate.getTime() + 86400000), |
| | }, |
| | { |
| | token: 'sort-new', |
| | userId: sortUserId, |
| | email: 'sort@example.com', |
| | createdAt: newDate, |
| | expiresAt: new Date(newDate.getTime() + 86400000), |
| | }, |
| | ]); |
| |
|
| | |
| | const found = await methods.findToken( |
| | { userId: sortUserId.toString() }, |
| | { sort: { createdAt: 1 } }, |
| | ); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('sort-old'); |
| | expect(found?.createdAt.getTime()).toBe(oldDate.getTime()); |
| | }); |
| |
|
| | test('should handle multiple sort criteria', async () => { |
| | const multiSortUserId = new mongoose.Types.ObjectId(); |
| | const sameDate = new Date(); |
| |
|
| | await Token.create([ |
| | { |
| | token: 'token-a', |
| | userId: multiSortUserId, |
| | email: 'z@example.com', |
| | createdAt: sameDate, |
| | expiresAt: new Date(sameDate.getTime() + 86400000), |
| | }, |
| | { |
| | token: 'token-b', |
| | userId: multiSortUserId, |
| | email: 'a@example.com', |
| | createdAt: sameDate, |
| | expiresAt: new Date(sameDate.getTime() + 86400000), |
| | }, |
| | { |
| | token: 'token-c', |
| | userId: multiSortUserId, |
| | email: 'm@example.com', |
| | createdAt: new Date(Date.now() - 1000), |
| | expiresAt: new Date(Date.now() + 86400000), |
| | }, |
| | ]); |
| |
|
| | |
| | const found = await methods.findToken( |
| | { userId: multiSortUserId.toString() }, |
| | { sort: { createdAt: -1, email: 1 } }, |
| | ); |
| |
|
| | expect(found).toBeDefined(); |
| | |
| | expect(found?.token).toBe('token-b'); |
| | expect(found?.email).toBe('a@example.com'); |
| | }); |
| |
|
| | test('should find token with projection option', async () => { |
| | const projectionUserId = new mongoose.Types.ObjectId(); |
| |
|
| | await Token.create({ |
| | token: 'projection-token', |
| | userId: projectionUserId, |
| | email: 'projection@example.com', |
| | identifier: 'oauth-projection', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 86400000), |
| | }); |
| |
|
| | |
| | const found = await methods.findToken( |
| | { userId: projectionUserId.toString() }, |
| | { projection: { token: 1, email: 1 } }, |
| | ); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('projection-token'); |
| | expect(found?.email).toBe('projection@example.com'); |
| | |
| | }); |
| |
|
| | test('should respect combined query options', async () => { |
| | const combinedUserId = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | await Token.create([ |
| | { |
| | token: 'combined-1', |
| | userId: combinedUserId, |
| | email: 'combined1@example.com', |
| | createdAt: new Date(Date.now() - 7200000), |
| | expiresAt: new Date(Date.now() + 86400000), |
| | }, |
| | { |
| | token: 'combined-2', |
| | userId: combinedUserId, |
| | email: 'combined2@example.com', |
| | createdAt: new Date(Date.now() - 3600000), |
| | expiresAt: new Date(Date.now() + 86400000), |
| | }, |
| | { |
| | token: 'combined-3', |
| | userId: combinedUserId, |
| | email: 'combined3@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 86400000), |
| | }, |
| | ]); |
| |
|
| | |
| | const found = await methods.findToken( |
| | { userId: combinedUserId.toString() }, |
| | { |
| | sort: { createdAt: -1 }, |
| | projection: { token: 1, createdAt: 1 }, |
| | }, |
| | ); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('combined-3'); |
| | expect(found?.createdAt).toBeDefined(); |
| | }); |
| | }); |
| |
|
| | describe('updateToken', () => { |
| | let updateUserId: mongoose.Types.ObjectId; |
| |
|
| | beforeEach(async () => { |
| | updateUserId = new mongoose.Types.ObjectId(); |
| | await Token.create({ |
| | token: 'update-token', |
| | userId: updateUserId, |
| | email: 'update@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }); |
| | }); |
| |
|
| | test('should update token by token value', async () => { |
| | const updated = await methods.updateToken( |
| | { token: 'update-token' }, |
| | { email: 'newemail@example.com' }, |
| | ); |
| |
|
| | expect(updated).toBeDefined(); |
| | expect(updated?.email).toBe('newemail@example.com'); |
| | expect(updated?.userId.toString()).toBe(updateUserId.toString()); |
| | }); |
| |
|
| | test('should update token by userId', async () => { |
| | const updated = await methods.updateToken( |
| | { userId: updateUserId.toString() }, |
| | { email: 'newemail@example.com' }, |
| | ); |
| |
|
| | expect(updated).toBeDefined(); |
| | expect(updated?.email).toBe('newemail@example.com'); |
| | expect(updated?.token).toBe('update-token'); |
| | }); |
| |
|
| | test('should return null for non-existent token', async () => { |
| | const updated = await methods.updateToken( |
| | { token: 'non-existent' }, |
| | { email: 'newemail@example.com' }, |
| | ); |
| |
|
| | expect(updated).toBeNull(); |
| | }); |
| |
|
| | test('should update expiresAt when expiresIn is provided', async () => { |
| | const beforeUpdate = Date.now(); |
| | const newExpiresIn = 7200; |
| |
|
| | const updated = await methods.updateToken( |
| | { token: 'update-token' }, |
| | { expiresIn: newExpiresIn }, |
| | ); |
| |
|
| | const afterUpdate = Date.now(); |
| |
|
| | expect(updated).toBeDefined(); |
| | expect(updated?.expiresAt).toBeDefined(); |
| |
|
| | const expectedMinExpiry = beforeUpdate + newExpiresIn * 1000; |
| | const expectedMaxExpiry = afterUpdate + newExpiresIn * 1000; |
| |
|
| | expect(updated!.expiresAt.getTime()).toBeGreaterThanOrEqual(expectedMinExpiry); |
| | expect(updated!.expiresAt.getTime()).toBeLessThanOrEqual(expectedMaxExpiry); |
| | }); |
| |
|
| | test('should not modify expiresAt when expiresIn is not provided', async () => { |
| | const original = await Token.findOne({ token: 'update-token' }); |
| | const originalExpiresAt = original!.expiresAt.getTime(); |
| |
|
| | const updated = await methods.updateToken( |
| | { token: 'update-token' }, |
| | { email: 'changed@example.com' }, |
| | ); |
| |
|
| | expect(updated).toBeDefined(); |
| | expect(updated?.email).toBe('changed@example.com'); |
| | expect(updated!.expiresAt.getTime()).toBe(originalExpiresAt); |
| | }); |
| | }); |
| |
|
| | describe('deleteTokens', () => { |
| | let user1Id: mongoose.Types.ObjectId; |
| | let user2Id: mongoose.Types.ObjectId; |
| | let user3Id: mongoose.Types.ObjectId; |
| | let oauthUserId: mongoose.Types.ObjectId; |
| |
|
| | beforeEach(async () => { |
| | user1Id = new mongoose.Types.ObjectId(); |
| | user2Id = new mongoose.Types.ObjectId(); |
| | user3Id = new mongoose.Types.ObjectId(); |
| | oauthUserId = new mongoose.Types.ObjectId(); |
| |
|
| | await Token.create([ |
| | { |
| | token: 'verify-token-1', |
| | userId: user1Id, |
| | email: 'user1@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }, |
| | { |
| | token: 'verify-token-2', |
| | userId: user2Id, |
| | email: 'user2@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }, |
| | { |
| | token: 'verify-token-3', |
| | userId: user3Id, |
| | email: 'user3@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }, |
| | { |
| | token: 'oauth-token', |
| | userId: oauthUserId, |
| | identifier: 'oauth-identifier-456', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }, |
| | ]); |
| | }); |
| |
|
| | test('should delete only tokens matching specific token value', async () => { |
| | const result = await methods.deleteTokens({ token: 'verify-token-1' }); |
| |
|
| | expect(result.deletedCount).toBe(1); |
| |
|
| | |
| | const remainingTokens = await Token.find({}); |
| | expect(remainingTokens).toHaveLength(3); |
| | expect(remainingTokens.find((t) => t.token === 'verify-token-2')).toBeDefined(); |
| | expect(remainingTokens.find((t) => t.token === 'verify-token-3')).toBeDefined(); |
| | expect(remainingTokens.find((t) => t.token === 'oauth-token')).toBeDefined(); |
| | }); |
| |
|
| | test('should delete only tokens matching specific userId', async () => { |
| | |
| | await Token.create({ |
| | token: 'another-user-1-token', |
| | userId: user1Id, |
| | email: 'user1@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }); |
| |
|
| | const result = await methods.deleteTokens({ userId: user1Id.toString() }); |
| |
|
| | expect(result.deletedCount).toBe(2); |
| |
|
| | const remainingTokens = await Token.find({}); |
| | expect(remainingTokens).toHaveLength(3); |
| | expect(remainingTokens.every((t) => t.userId.toString() !== user1Id.toString())).toBe(true); |
| | }); |
| |
|
| | test('should delete only tokens matching specific email', async () => { |
| | const result = await methods.deleteTokens({ email: 'user2@example.com' }); |
| |
|
| | expect(result.deletedCount).toBe(1); |
| |
|
| | const remainingTokens = await Token.find({}); |
| | expect(remainingTokens).toHaveLength(3); |
| | expect(remainingTokens.find((t) => t.email === 'user2@example.com')).toBeUndefined(); |
| | }); |
| |
|
| | test('should delete only tokens matching specific identifier', async () => { |
| | const result = await methods.deleteTokens({ identifier: 'oauth-identifier-456' }); |
| |
|
| | expect(result.deletedCount).toBe(1); |
| |
|
| | const remainingTokens = await Token.find({}); |
| | expect(remainingTokens).toHaveLength(3); |
| | expect(remainingTokens.find((t) => t.identifier === 'oauth-identifier-456')).toBeUndefined(); |
| | }); |
| |
|
| | test('should not delete tokens when undefined fields are passed', async () => { |
| | |
| | const result = await methods.deleteTokens({ |
| | token: 'verify-token-1', |
| | identifier: undefined, |
| | userId: undefined, |
| | email: undefined, |
| | }); |
| |
|
| | expect(result.deletedCount).toBe(1); |
| |
|
| | const remainingTokens = await Token.find({}); |
| | expect(remainingTokens).toHaveLength(3); |
| | }); |
| |
|
| | test('should delete multiple tokens when they match OR conditions', async () => { |
| | |
| | await Token.create({ |
| | token: 'multi-match', |
| | userId: user2Id, |
| | email: 'different@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }); |
| |
|
| | const result = await methods.deleteTokens({ |
| | token: 'verify-token-1', |
| | userId: user2Id.toString(), |
| | }); |
| |
|
| | |
| | expect(result.deletedCount).toBe(3); |
| |
|
| | const remainingTokens = await Token.find({}); |
| | expect(remainingTokens).toHaveLength(2); |
| | }); |
| |
|
| | test('should throw error when no query parameters provided', async () => { |
| | await expect(methods.deleteTokens({})).rejects.toThrow( |
| | 'At least one query parameter must be provided', |
| | ); |
| |
|
| | |
| | const tokens = await Token.find({}); |
| | expect(tokens).toHaveLength(4); |
| | }); |
| |
|
| | test('should handle deletion when no tokens match', async () => { |
| | const result = await methods.deleteTokens({ token: 'non-existent-token' }); |
| |
|
| | expect(result.deletedCount).toBe(0); |
| |
|
| | |
| | const tokens = await Token.find({}); |
| | expect(tokens).toHaveLength(4); |
| | }); |
| |
|
| | test('should handle email verification scenario correctly', async () => { |
| | |
| | |
| | const newUser1Id = new mongoose.Types.ObjectId(); |
| | const newUser2Id = new mongoose.Types.ObjectId(); |
| | const newUser3Id = new mongoose.Types.ObjectId(); |
| |
|
| | await Token.create([ |
| | { |
| | token: 'email-verify-token-1', |
| | userId: newUser1Id, |
| | email: 'newuser1@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 86400000), |
| | }, |
| | { |
| | token: 'email-verify-token-2', |
| | userId: newUser2Id, |
| | email: 'newuser2@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 86400000), |
| | }, |
| | { |
| | token: 'email-verify-token-3', |
| | userId: newUser3Id, |
| | email: 'newuser3@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 86400000), |
| | }, |
| | ]); |
| |
|
| | |
| | const result = await methods.deleteTokens({ token: 'email-verify-token-2' }); |
| |
|
| | expect(result.deletedCount).toBe(1); |
| |
|
| | |
| | const remainingTokens = await Token.find({ token: { $regex: /^email-verify-token-/ } }); |
| | expect(remainingTokens).toHaveLength(2); |
| | expect(remainingTokens.find((t) => t.token === 'email-verify-token-1')).toBeDefined(); |
| | expect(remainingTokens.find((t) => t.token === 'email-verify-token-3')).toBeDefined(); |
| | expect(remainingTokens.find((t) => t.token === 'email-verify-token-2')).toBeUndefined(); |
| | }); |
| | }); |
| |
|
| | describe('Email Normalization', () => { |
| | let normUserId: mongoose.Types.ObjectId; |
| |
|
| | beforeEach(async () => { |
| | normUserId = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | await Token.create({ |
| | token: 'norm-token-1', |
| | userId: normUserId, |
| | email: 'john.doe@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }); |
| | }); |
| |
|
| | describe('findToken email normalization', () => { |
| | test('should find token by email with different case (case-insensitive)', async () => { |
| | const foundUpper = await methods.findToken({ email: 'JOHN.DOE@EXAMPLE.COM' }); |
| | const foundMixed = await methods.findToken({ email: 'John.Doe@Example.COM' }); |
| | const foundLower = await methods.findToken({ email: 'john.doe@example.com' }); |
| |
|
| | expect(foundUpper).toBeDefined(); |
| | expect(foundUpper?.token).toBe('norm-token-1'); |
| |
|
| | expect(foundMixed).toBeDefined(); |
| | expect(foundMixed?.token).toBe('norm-token-1'); |
| |
|
| | expect(foundLower).toBeDefined(); |
| | expect(foundLower?.token).toBe('norm-token-1'); |
| | }); |
| |
|
| | test('should find token by email with leading/trailing whitespace', async () => { |
| | const foundWithSpaces = await methods.findToken({ email: ' john.doe@example.com ' }); |
| | const foundWithTabs = await methods.findToken({ email: '\tjohn.doe@example.com\t' }); |
| |
|
| | expect(foundWithSpaces).toBeDefined(); |
| | expect(foundWithSpaces?.token).toBe('norm-token-1'); |
| |
|
| | expect(foundWithTabs).toBeDefined(); |
| | expect(foundWithTabs?.token).toBe('norm-token-1'); |
| | }); |
| |
|
| | test('should find token by email with both case difference and whitespace', async () => { |
| | const found = await methods.findToken({ email: ' JOHN.DOE@EXAMPLE.COM ' }); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('norm-token-1'); |
| | }); |
| |
|
| | test('should find token with combined email and other criteria', async () => { |
| | const found = await methods.findToken({ |
| | userId: normUserId.toString(), |
| | email: 'John.Doe@Example.COM', |
| | }); |
| |
|
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('norm-token-1'); |
| | }); |
| | }); |
| |
|
| | describe('deleteTokens email normalization', () => { |
| | test('should delete token by email with different case', async () => { |
| | const result = await methods.deleteTokens({ email: 'JOHN.DOE@EXAMPLE.COM' }); |
| |
|
| | expect(result.deletedCount).toBe(1); |
| |
|
| | const remaining = await Token.find({}); |
| | expect(remaining).toHaveLength(0); |
| | }); |
| |
|
| | test('should delete token by email with whitespace', async () => { |
| | const result = await methods.deleteTokens({ email: ' john.doe@example.com ' }); |
| |
|
| | expect(result.deletedCount).toBe(1); |
| |
|
| | const remaining = await Token.find({}); |
| | expect(remaining).toHaveLength(0); |
| | }); |
| |
|
| | test('should delete token by email with case and whitespace combined', async () => { |
| | const result = await methods.deleteTokens({ email: ' John.Doe@EXAMPLE.COM ' }); |
| |
|
| | expect(result.deletedCount).toBe(1); |
| |
|
| | const remaining = await Token.find({}); |
| | expect(remaining).toHaveLength(0); |
| | }); |
| |
|
| | test('should only delete matching token when using normalized email', async () => { |
| | |
| | await Token.create({ |
| | token: 'norm-token-2', |
| | userId: new mongoose.Types.ObjectId(), |
| | email: 'jane.doe@example.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 3600000), |
| | }); |
| |
|
| | const result = await methods.deleteTokens({ email: 'JOHN.DOE@EXAMPLE.COM' }); |
| |
|
| | expect(result.deletedCount).toBe(1); |
| |
|
| | const remaining = await Token.find({}); |
| | expect(remaining).toHaveLength(1); |
| | expect(remaining[0].email).toBe('jane.doe@example.com'); |
| | }); |
| | }); |
| |
|
| | describe('Email verification flow with normalization', () => { |
| | test('should handle OpenID provider email case mismatch scenario', async () => { |
| | |
| | |
| | |
| | |
| | |
| | |
| | const userId = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | await Token.create({ |
| | token: 'verification-token', |
| | userId: userId, |
| | email: 'user@company.com', |
| | createdAt: new Date(), |
| | expiresAt: new Date(Date.now() + 86400000), |
| | }); |
| |
|
| | |
| | const emailFromProvider = 'User@Company.COM'; |
| |
|
| | |
| | const found = await methods.findToken({ email: emailFromProvider }); |
| | expect(found).toBeDefined(); |
| | expect(found?.token).toBe('verification-token'); |
| |
|
| | |
| | const deleted = await methods.deleteTokens({ email: emailFromProvider }); |
| | expect(deleted.deletedCount).toBe(1); |
| | }); |
| |
|
| | test('should handle resend verification email with case mismatch', async () => { |
| | const userId = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | await Token.create({ |
| | token: 'old-verification', |
| | userId: userId, |
| | email: 'john.smith@enterprise.com', |
| | createdAt: new Date(Date.now() - 3600000), |
| | expiresAt: new Date(Date.now() + 82800000), |
| | }); |
| |
|
| | |
| | const userInputEmail = ' John.Smith@ENTERPRISE.COM '; |
| |
|
| | |
| | const deleted = await methods.deleteTokens({ email: userInputEmail }); |
| | expect(deleted.deletedCount).toBe(1); |
| |
|
| | |
| | const remaining = await Token.find({ userId }); |
| | expect(remaining).toHaveLength(0); |
| | }); |
| | }); |
| | }); |
| | }); |
| |
|