redis pt 5: fix tests
This commit is contained in:
@@ -4,8 +4,11 @@ import {
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { decode, sign } from 'jsonwebtoken';
|
||||
import { CacheService } from '../common/cache/cache.service';
|
||||
import { CacheTagsService } from '../common/cache/cache-tags.service';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
import { AuthService } from './auth.service';
|
||||
import { sha256 } from './auth.utils';
|
||||
@@ -58,6 +61,27 @@ describe('AuthService', () => {
|
||||
$transaction: jest.fn(),
|
||||
};
|
||||
|
||||
const mockEventEmitter = {
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCacheService = {
|
||||
get: jest.fn().mockResolvedValue(null),
|
||||
set: jest.fn().mockResolvedValue(true),
|
||||
del: jest.fn().mockResolvedValue(true),
|
||||
getNamespacedKey: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(namespace: string, key: string) => `friendolls:${namespace}:${key}`,
|
||||
),
|
||||
recordError: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCacheTagsService = {
|
||||
rememberKeyForTag: jest.fn().mockResolvedValue(undefined),
|
||||
invalidateTag: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
const socialProfile: SocialAuthProfile = {
|
||||
provider: 'google',
|
||||
providerSubject: 'google-user-123',
|
||||
@@ -94,6 +118,9 @@ describe('AuthService', () => {
|
||||
AuthService,
|
||||
{ provide: PrismaService, useValue: mockPrismaService },
|
||||
{ provide: ConfigService, useValue: mockConfigService },
|
||||
{ provide: EventEmitter2, useValue: mockEventEmitter },
|
||||
{ provide: CacheService, useValue: mockCacheService },
|
||||
{ provide: CacheTagsService, useValue: mockCacheTagsService },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -135,6 +162,9 @@ describe('AuthService', () => {
|
||||
const localService = new AuthService(
|
||||
mockPrismaService as unknown as PrismaService,
|
||||
mockConfigService as unknown as ConfigService,
|
||||
mockEventEmitter as unknown as EventEmitter2,
|
||||
mockCacheService as unknown as CacheService,
|
||||
mockCacheTagsService as unknown as CacheTagsService,
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
|
||||
@@ -4,6 +4,9 @@ import { DollsService } from './dolls.service';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
import { NotFoundException, ForbiddenException } from '@nestjs/common';
|
||||
import { Doll } from '@prisma/client';
|
||||
import { CacheService } from '../common/cache/cache.service';
|
||||
import { CacheTagsService } from '../common/cache/cache-tags.service';
|
||||
import { FriendsService } from '../friends/friends.service';
|
||||
|
||||
describe('DollsService', () => {
|
||||
let service: DollsService;
|
||||
@@ -31,9 +34,6 @@ describe('DollsService', () => {
|
||||
findFirst: jest.fn().mockResolvedValue(mockDoll),
|
||||
update: jest.fn().mockResolvedValue(mockDoll),
|
||||
},
|
||||
friendship: {
|
||||
findMany: jest.fn().mockResolvedValue([]),
|
||||
},
|
||||
$transaction: jest.fn((callback) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return callback(mockPrismaService);
|
||||
@@ -47,6 +47,25 @@ describe('DollsService', () => {
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCacheService = {
|
||||
get: jest.fn().mockResolvedValue(null),
|
||||
set: jest.fn().mockResolvedValue(true),
|
||||
getNamespacedKey: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(namespace: string, key: string) => `friendolls:${namespace}:${key}`,
|
||||
),
|
||||
recordError: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCacheTagsService = {
|
||||
rememberKeyForTag: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
const mockFriendsService = {
|
||||
areFriends: jest.fn().mockResolvedValue(false),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
@@ -59,6 +78,18 @@ describe('DollsService', () => {
|
||||
provide: EventEmitter2,
|
||||
useValue: mockEventEmitter,
|
||||
},
|
||||
{
|
||||
provide: CacheService,
|
||||
useValue: mockCacheService,
|
||||
},
|
||||
{
|
||||
provide: CacheTagsService,
|
||||
useValue: mockCacheTagsService,
|
||||
},
|
||||
{
|
||||
provide: FriendsService,
|
||||
useValue: mockFriendsService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -112,10 +143,7 @@ describe('DollsService', () => {
|
||||
const ownerId = 'friend-1';
|
||||
const requestingUserId = 'user-1';
|
||||
|
||||
// Mock friendship
|
||||
jest
|
||||
.spyOn(prismaService.friendship, 'findMany')
|
||||
.mockResolvedValueOnce([{ friendId: ownerId } as any]);
|
||||
(mockFriendsService.areFriends as jest.Mock).mockResolvedValueOnce(true);
|
||||
|
||||
await service.listByOwner(ownerId, requestingUserId);
|
||||
|
||||
@@ -134,10 +162,7 @@ describe('DollsService', () => {
|
||||
const ownerId = 'stranger-1';
|
||||
const requestingUserId = 'user-1';
|
||||
|
||||
// Mock empty friendship (default)
|
||||
jest
|
||||
.spyOn(prismaService.friendship, 'findMany')
|
||||
.mockResolvedValueOnce([]);
|
||||
(mockFriendsService.areFriends as jest.Mock).mockResolvedValueOnce(false);
|
||||
|
||||
await expect(
|
||||
service.listByOwner(ownerId, requestingUserId),
|
||||
@@ -163,7 +188,10 @@ describe('DollsService', () => {
|
||||
});
|
||||
|
||||
it('should throw NotFoundException if doll not accessible', async () => {
|
||||
jest.spyOn(prismaService.doll, 'findFirst').mockResolvedValueOnce(null);
|
||||
jest
|
||||
.spyOn(prismaService.doll, 'findFirst')
|
||||
.mockResolvedValueOnce({ ...mockDoll, userId: 'user-2' });
|
||||
(mockFriendsService.areFriends as jest.Mock).mockResolvedValueOnce(false);
|
||||
|
||||
await expect(service.findOne('doll-1', 'user-1')).rejects.toThrow(
|
||||
NotFoundException,
|
||||
@@ -179,7 +207,7 @@ describe('DollsService', () => {
|
||||
expect(prismaService.doll.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw ForbiddenException if not owner', async () => {
|
||||
it('should throw NotFoundException if not owner and not a friend', async () => {
|
||||
jest
|
||||
.spyOn(prismaService.doll, 'findFirst')
|
||||
.mockResolvedValueOnce({ ...mockDoll, userId: 'user-2' });
|
||||
@@ -187,7 +215,7 @@ describe('DollsService', () => {
|
||||
const updateDto = { name: 'Updated Doll' };
|
||||
await expect(
|
||||
service.update('doll-1', 'user-1', updateDto),
|
||||
).rejects.toThrow(ForbiddenException);
|
||||
).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -203,13 +231,13 @@ describe('DollsService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw ForbiddenException if not owner', async () => {
|
||||
it('should throw NotFoundException if not owner and not a friend', async () => {
|
||||
jest
|
||||
.spyOn(prismaService.doll, 'findFirst')
|
||||
.mockResolvedValueOnce({ ...mockDoll, userId: 'user-2' });
|
||||
|
||||
await expect(service.remove('doll-1', 'user-1')).rejects.toThrow(
|
||||
ForbiddenException,
|
||||
NotFoundException,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { FriendsService } from './friends.service';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
import { CacheService } from '../common/cache/cache.service';
|
||||
import { CacheTagsService } from '../common/cache/cache-tags.service';
|
||||
import {
|
||||
NotFoundException,
|
||||
BadRequestException,
|
||||
@@ -17,6 +19,8 @@ enum FriendRequestStatus {
|
||||
describe('FriendsService', () => {
|
||||
let service: FriendsService;
|
||||
let eventEmitter: EventEmitter2;
|
||||
let cacheService: CacheService;
|
||||
let cacheTagsService: CacheTagsService;
|
||||
|
||||
const mockUser1 = {
|
||||
id: 'user-1',
|
||||
@@ -90,6 +94,21 @@ describe('FriendsService', () => {
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCacheService = {
|
||||
get: jest.fn().mockResolvedValue(null),
|
||||
set: jest.fn().mockResolvedValue(true),
|
||||
getNamespacedKey: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(namespace: string, key: string) => `friendolls:${namespace}:${key}`,
|
||||
),
|
||||
recordError: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCacheTagsService = {
|
||||
rememberKeyForTag: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
@@ -102,11 +121,21 @@ describe('FriendsService', () => {
|
||||
provide: EventEmitter2,
|
||||
useValue: mockEventEmitter,
|
||||
},
|
||||
{
|
||||
provide: CacheService,
|
||||
useValue: mockCacheService,
|
||||
},
|
||||
{
|
||||
provide: CacheTagsService,
|
||||
useValue: mockCacheTagsService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<FriendsService>(FriendsService);
|
||||
eventEmitter = module.get<EventEmitter2>(EventEmitter2);
|
||||
cacheService = module.get<CacheService>(CacheService);
|
||||
cacheTagsService = module.get<CacheTagsService>(CacheTagsService);
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -420,6 +449,8 @@ describe('FriendsService', () => {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
});
|
||||
expect(cacheService.set).toHaveBeenCalled();
|
||||
expect(cacheTagsService.rememberKeyForTag).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -469,6 +500,12 @@ describe('FriendsService', () => {
|
||||
const result = await service.areFriends('user-1', 'user-2');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(cacheService.set).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'1',
|
||||
expect.any(Number),
|
||||
);
|
||||
expect(cacheTagsService.rememberKeyForTag).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return false when users are not friends', async () => {
|
||||
@@ -477,6 +514,11 @@ describe('FriendsService', () => {
|
||||
const result = await service.areFriends('user-1', 'user-2');
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(cacheService.set).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'0',
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,9 +5,13 @@ import { NotFoundException, ForbiddenException } from '@nestjs/common';
|
||||
import { User } from '@prisma/client';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { CacheService } from '../common/cache/cache.service';
|
||||
import { CacheTagsService } from '../common/cache/cache-tags.service';
|
||||
|
||||
describe('UsersService', () => {
|
||||
let service: UsersService;
|
||||
let cacheService: CacheService;
|
||||
let cacheTagsService: CacheTagsService;
|
||||
|
||||
const mockUser: User & { passwordHash?: string | null } = {
|
||||
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||
@@ -39,6 +43,21 @@ describe('UsersService', () => {
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCacheService = {
|
||||
get: jest.fn().mockResolvedValue(null),
|
||||
set: jest.fn().mockResolvedValue(true),
|
||||
getNamespacedKey: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(namespace: string, key: string) => `friendolls:${namespace}:${key}`,
|
||||
),
|
||||
recordError: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCacheTagsService = {
|
||||
rememberKeyForTag: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
@@ -51,10 +70,20 @@ describe('UsersService', () => {
|
||||
provide: EventEmitter2,
|
||||
useValue: mockEventEmitter,
|
||||
},
|
||||
{
|
||||
provide: CacheService,
|
||||
useValue: mockCacheService,
|
||||
},
|
||||
{
|
||||
provide: CacheTagsService,
|
||||
useValue: mockCacheTagsService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UsersService>(UsersService);
|
||||
cacheService = module.get<CacheService>(CacheService);
|
||||
cacheTagsService = module.get<CacheTagsService>(CacheTagsService);
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -227,6 +256,8 @@ describe('UsersService', () => {
|
||||
username: 'asc',
|
||||
},
|
||||
});
|
||||
expect(cacheService.set).toHaveBeenCalled();
|
||||
expect(cacheTagsService.rememberKeyForTag).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should exclude specified user from results', async () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { JwtVerificationService } from '../../auth/services/jwt-verification.ser
|
||||
import { PrismaService } from '../../database/prisma.service';
|
||||
import { UserSocketService } from './user-socket.service';
|
||||
import { WsNotificationService } from './ws-notification.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { SendInteractionDto } from '../dto/send-interaction.dto';
|
||||
import { WsException } from '@nestjs/websockets';
|
||||
|
||||
@@ -45,6 +46,7 @@ describe('StateGateway', () => {
|
||||
let mockUserSocketService: Partial<UserSocketService>;
|
||||
let mockRedisClient: { publish: jest.Mock };
|
||||
let mockRedisSubscriber: { subscribe: jest.Mock; on: jest.Mock };
|
||||
let mockConfigService: { get: jest.Mock };
|
||||
let mockWsNotificationService: {
|
||||
setIo: jest.Mock;
|
||||
emitToUser: jest.Mock;
|
||||
@@ -52,6 +54,8 @@ describe('StateGateway', () => {
|
||||
emitToSocket: jest.Mock;
|
||||
updateActiveDollCache: jest.Mock;
|
||||
publishActiveDollUpdate: jest.Mock;
|
||||
clearSenderNameCache: jest.Mock;
|
||||
maybeTouchPresence: jest.Mock;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -92,9 +96,12 @@ describe('StateGateway', () => {
|
||||
mockUserSocketService = {
|
||||
setSocket: jest.fn().mockResolvedValue(undefined),
|
||||
removeSocket: jest.fn().mockResolvedValue(undefined),
|
||||
removeSocketById: jest.fn().mockResolvedValue(undefined),
|
||||
touchLastSeen: jest.fn().mockResolvedValue(undefined),
|
||||
getSocket: jest.fn().mockResolvedValue(null),
|
||||
isUserOnline: jest.fn().mockResolvedValue(false),
|
||||
getFriendsSockets: jest.fn().mockResolvedValue([]),
|
||||
cleanupStalePresence: jest.fn().mockResolvedValue(0),
|
||||
};
|
||||
|
||||
mockRedisClient = {
|
||||
@@ -106,6 +113,10 @@ describe('StateGateway', () => {
|
||||
on: jest.fn(),
|
||||
};
|
||||
|
||||
mockConfigService = {
|
||||
get: jest.fn().mockReturnValue(undefined),
|
||||
};
|
||||
|
||||
mockWsNotificationService = {
|
||||
setIo: jest.fn(),
|
||||
emitToUser: jest.fn(),
|
||||
@@ -113,6 +124,8 @@ describe('StateGateway', () => {
|
||||
emitToSocket: jest.fn(),
|
||||
updateActiveDollCache: jest.fn(),
|
||||
publishActiveDollUpdate: jest.fn(),
|
||||
clearSenderNameCache: jest.fn().mockResolvedValue(undefined),
|
||||
maybeTouchPresence: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -125,6 +138,7 @@ describe('StateGateway', () => {
|
||||
{ provide: PrismaService, useValue: mockPrismaService },
|
||||
{ provide: UserSocketService, useValue: mockUserSocketService },
|
||||
{ provide: WsNotificationService, useValue: mockWsNotificationService },
|
||||
{ provide: ConfigService, useValue: mockConfigService },
|
||||
{ provide: 'REDIS_CLIENT', useValue: mockRedisClient },
|
||||
{ provide: 'REDIS_SUBSCRIBER_CLIENT', useValue: mockRedisSubscriber },
|
||||
],
|
||||
@@ -161,9 +175,32 @@ describe('StateGateway', () => {
|
||||
expect(mockRedisSubscriber.subscribe).toHaveBeenCalledWith(
|
||||
'active-doll-update',
|
||||
'friend-cache-update',
|
||||
'user-profile-cache-invalidate',
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should route user profile cache invalidation messages', async () => {
|
||||
gateway.afterInit();
|
||||
|
||||
const onCalls = (mockRedisSubscriber.on as jest.Mock).mock.calls;
|
||||
const messageHandler = onCalls.find(
|
||||
(call) => call[0] === 'message',
|
||||
)?.[1] as ((channel: string, message: string) => void) | undefined;
|
||||
|
||||
expect(messageHandler).toBeDefined();
|
||||
|
||||
messageHandler?.(
|
||||
'user-profile-cache-invalidate',
|
||||
JSON.stringify({ userId: 'user-1' }),
|
||||
);
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
expect(
|
||||
mockWsNotificationService.clearSenderNameCache,
|
||||
).toHaveBeenCalledWith('user-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleConnection', () => {
|
||||
@@ -260,6 +297,9 @@ describe('StateGateway', () => {
|
||||
'user-id',
|
||||
'client1',
|
||||
);
|
||||
expect(mockUserSocketService.touchLastSeen).toHaveBeenCalledWith(
|
||||
'user-id',
|
||||
);
|
||||
|
||||
// 2. Fetch State (DB)
|
||||
expect(mockPrismaService.user!.findUnique).toHaveBeenCalledWith({
|
||||
@@ -359,6 +399,13 @@ describe('StateGateway', () => {
|
||||
expect(mockUserSocketService.getSocket).toHaveBeenCalledWith('user-id');
|
||||
expect(mockUserSocketService.removeSocket).toHaveBeenCalledWith(
|
||||
'user-id',
|
||||
'client1',
|
||||
);
|
||||
expect(mockUserSocketService.touchLastSeen).toHaveBeenCalledWith(
|
||||
'user-id',
|
||||
);
|
||||
expect(mockUserSocketService.removeSocketById).toHaveBeenCalledWith(
|
||||
'client1',
|
||||
);
|
||||
expect(mockWsNotificationService.emitToSocket).toHaveBeenCalledWith(
|
||||
'friend-socket-id',
|
||||
|
||||
Reference in New Issue
Block a user