Spaces:
Running
Running
Download apps/api/src/network/network.service.spec.ts from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 16 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/src/network/network.service.spec.ts
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/apps/api/src/network/network.service.spec.ts
-
curl -L -o network.service.spec.ts https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/src/network/network.service.spec.ts
16 kB
| import { Test, TestingModule } from "@nestjs/testing"; | |
| import { NotFoundException, ConflictException, ForbiddenException, BadRequestException } from "@nestjs/common"; | |
| import { NetworkService } from "./network.service"; | |
| import { PrismaService } from "../prisma/prisma.service"; | |
| import { MeilisearchService } from "./meilisearch.service"; | |
| import { ReputationEngine } from "./reputation.service"; | |
| import { GraphService } from "./graph.service"; | |
| import { AuthorizationService } from "../common/authorization.service"; | |
| import { RedisRateLimiterService } from "../common/redis-rate-limiter.service"; | |
| import { getQueueToken } from "@nestjs/bullmq"; | |
| describe("NetworkService", () => { | |
| let service: NetworkService; | |
| const mockPrisma = { | |
| guestPublicProfile: { | |
| findUnique: jest.fn(), | |
| findFirst: jest.fn(), | |
| findMany: jest.fn(), | |
| create: jest.fn(), | |
| update: jest.fn(), | |
| count: jest.fn(), | |
| }, | |
| networkBookingRequest: { | |
| findUnique: jest.fn(), | |
| findFirst: jest.fn(), | |
| findMany: jest.fn(), | |
| create: jest.fn(), | |
| update: jest.fn(), | |
| count: jest.fn(), | |
| }, | |
| networkBookmark: { | |
| upsert: jest.fn(), | |
| updateMany: jest.fn(), | |
| }, | |
| hostReview: { | |
| findUnique: jest.fn(), | |
| create: jest.fn(), | |
| }, | |
| profileView: { | |
| findFirst: jest.fn(), | |
| create: jest.fn(), | |
| update: jest.fn(), | |
| count: jest.fn(), | |
| groupBy: jest.fn(), | |
| }, | |
| expertiseTag: { | |
| deleteMany: jest.fn(), | |
| createMany: jest.fn(), | |
| }, | |
| availabilityWindow: { | |
| deleteMany: jest.fn(), | |
| createMany: jest.fn(), | |
| }, | |
| guest: { | |
| findUnique: jest.fn(), | |
| findFirst: jest.fn(), | |
| create: jest.fn(), | |
| update: jest.fn(), | |
| }, | |
| episode: { | |
| findFirst: jest.fn(), | |
| create: jest.fn(), | |
| update: jest.fn(), | |
| }, | |
| booking: { | |
| findFirst: jest.fn(), | |
| create: jest.fn(), | |
| }, | |
| bookingHistory: { | |
| findFirst: jest.fn(), | |
| create: jest.fn(), | |
| }, | |
| guestJourneyEvent: { | |
| create: jest.fn(), | |
| }, | |
| user: { | |
| findUnique: jest.fn(), | |
| }, | |
| podcast: { | |
| findUnique: jest.fn(), | |
| }, | |
| $transaction: jest.fn(), | |
| $queryRaw: jest.fn(), | |
| }; | |
| mockPrisma.$transaction.mockImplementation(<T>(cb: (tx: typeof mockPrisma) => Promise<T>): Promise<T> => cb(mockPrisma)); | |
| const mockMeilisearch = { | |
| indexProfile: jest.fn(), | |
| removeProfile: jest.fn(), | |
| updateProfile: jest.fn(), | |
| searchProfiles: jest.fn(), | |
| }; | |
| const mockReputationEngine = { | |
| calculate: jest.fn(), | |
| getInsights: jest.fn(), | |
| }; | |
| const mockGraphService = { | |
| ingestProfile: jest.fn(), | |
| getRecommendations: jest.fn(), | |
| getNetworkStats: jest.fn(), | |
| }; | |
| const mockQueue = { | |
| add: jest.fn(), | |
| }; | |
| const mockAuthorizationService = { | |
| checkPodcastAccess: jest.fn().mockResolvedValue(undefined), | |
| checkAdminAccess: jest.fn().mockResolvedValue(undefined), | |
| }; | |
| const mockWorkspaceMember = { | |
| findFirst: jest.fn().mockResolvedValue({ id: "mock-wm-id" }), | |
| }; | |
| const mockPrismaWithWorkspaceMember = { | |
| ...mockPrisma, | |
| workspaceMember: mockWorkspaceMember, | |
| }; | |
| const mockRateLimiter = { | |
| checkLimit: jest.fn().mockResolvedValue({ allowed: true, remaining: 9, resetAt: new Date() }), | |
| }; | |
| beforeEach(async () => { | |
| // NOTE: the service now depends on both `mockPrisma` and the dedicated | |
| // `mockWorkspaceMember` double, so reset all of them explicitly (not just | |
| // `mockPrisma`) so every test starts from a clean mock surface. | |
| const module: TestingModule = await Test.createTestingModule({ | |
| providers: [ | |
| NetworkService, | |
| { provide: PrismaService, useValue: mockPrismaWithWorkspaceMember }, | |
| { provide: AuthorizationService, useValue: mockAuthorizationService }, | |
| { provide: RedisRateLimiterService, useValue: mockRateLimiter }, | |
| { provide: MeilisearchService, useValue: mockMeilisearch }, | |
| { provide: ReputationEngine, useValue: mockReputationEngine }, | |
| { provide: GraphService, useValue: mockGraphService }, | |
| { provide: getQueueToken("network"), useValue: mockQueue }, | |
| ], | |
| }).compile(); | |
| service = module.get<NetworkService>(NetworkService); | |
| jest.clearAllMocks(); | |
| mockRateLimiter.checkLimit.mockResolvedValue({ allowed: true, remaining: 9, resetAt: new Date() }); | |
| }); | |
| afterEach(() => { | |
| jest.restoreAllMocks(); | |
| }); | |
| // βββ Outbound Request Rate Limiting ββββββββββββββββββ | |
| describe("createBookingRequest β outbound rate limiting", () => { | |
| it("should allow a request when the user is under the hourly cap", async () => { | |
| mockRateLimiter.checkLimit.mockResolvedValue({ allowed: true, remaining: 9, resetAt: new Date() }); | |
| mockPrisma.guestPublicProfile.findUnique.mockResolvedValue({ | |
| id: "profile-1", | |
| isPublic: true, | |
| verificationStatus: "verified", | |
| userId: "user-other", | |
| user: { email: "guest@test.com" }, | |
| }); | |
| mockPrisma.podcast.findUnique.mockResolvedValue({ | |
| workspace: { members: [{ userId: "user-self" }] }, | |
| }); | |
| mockPrisma.networkBookingRequest.count.mockResolvedValue(0); | |
| mockPrisma.networkBookingRequest.create.mockResolvedValue({ | |
| id: "req-new", | |
| toProfile: { headline: "Guest", visibilitySettings: {}, phoneNumber: null, whatsappNumber: null, user: { email: "guest@test.com" } }, | |
| fromPodcast: { title: "Podcast" }, | |
| }); | |
| await expect( | |
| service.createBookingRequest({ | |
| toProfileId: "profile-1", | |
| fromPodcastId: "podcast-1", | |
| message: "Let's chat", | |
| proposedTopics: [], | |
| proposedDates: [], | |
| }, "test-user") | |
| ).resolves.toHaveProperty("id", "req-new"); | |
| expect(mockRateLimiter.checkLimit).toHaveBeenCalledWith("user:test-user:booking-request", { | |
| max: 20, | |
| windowMs: 3_600_000, | |
| keyPrefix: "network:booking", | |
| }); | |
| }); | |
| it("should reject a request when the user has exhausted the hourly cap", async () => { | |
| mockRateLimiter.checkLimit.mockResolvedValue({ allowed: false, remaining: 0, resetAt: new Date() }); | |
| await expect( | |
| service.createBookingRequest({ | |
| toProfileId: "profile-1", | |
| fromPodcastId: "podcast-1", | |
| message: "Let's chat", | |
| proposedTopics: [], | |
| proposedDates: [], | |
| }, "test-user") | |
| ).rejects.toThrow(BadRequestException); | |
| }); | |
| }); | |
| // βββ Self-Booking Prevention βββββββββββββββββββββββββ | |
| describe("createBookingRequest β self-booking prevention", () => { | |
| it("should reject booking request to own profile", async () => { | |
| mockPrisma.guestPublicProfile.findUnique.mockResolvedValue({ | |
| id: "profile-1", | |
| isPublic: true, | |
| verificationStatus: "verified", | |
| userId: "user-own", | |
| user: { email: "guest@test.com" }, | |
| }); | |
| mockPrisma.podcast.findUnique.mockResolvedValue({ | |
| workspace: { | |
| members: [{ userId: "user-own" }], | |
| }, | |
| }); | |
| await expect( | |
| service.createBookingRequest({ | |
| toProfileId: "profile-1", | |
| fromPodcastId: "podcast-1", | |
| message: "This is a test message that is long enough", | |
| proposedTopics: [], | |
| proposedDates: [], | |
| }, "test-user-123") | |
| ).rejects.toThrow(ForbiddenException); | |
| }); | |
| it("should reject duplicate pending requests", async () => { | |
| mockPrisma.guestPublicProfile.findUnique.mockResolvedValue({ | |
| id: "profile-1", | |
| isPublic: true, | |
| verificationStatus: "verified", | |
| userId: "user-other", | |
| user: { email: "guest@test.com" }, | |
| }); | |
| mockPrisma.podcast.findUnique.mockResolvedValue({ | |
| workspace: { | |
| members: [{ userId: "user-self" }], | |
| }, | |
| }); | |
| // Already 3 pending requests | |
| mockPrisma.networkBookingRequest.count.mockResolvedValue(3); | |
| await expect( | |
| service.createBookingRequest({ | |
| toProfileId: "profile-1", | |
| fromPodcastId: "podcast-1", | |
| message: "This is a test message that is long enough", | |
| proposedTopics: [], | |
| proposedDates: [], | |
| }, "test-user-123") | |
| ).rejects.toThrow(ConflictException); | |
| }); | |
| it("should reject proposed dates in the past", async () => { | |
| mockPrisma.guestPublicProfile.findUnique.mockResolvedValue({ | |
| id: "profile-1", | |
| isPublic: true, | |
| verificationStatus: "verified", | |
| userId: "user-other", | |
| user: { email: "guest@test.com" }, | |
| }); | |
| mockPrisma.podcast.findUnique.mockResolvedValue({ | |
| workspace: { | |
| members: [{ userId: "user-self" }], | |
| }, | |
| }); | |
| mockPrisma.networkBookingRequest.count.mockResolvedValue(0); | |
| const pastDate = new Date(Date.now() - 86400000).toISOString(); | |
| await expect( | |
| service.createBookingRequest({ | |
| toProfileId: "profile-1", | |
| fromPodcastId: "podcast-1", | |
| message: "This is a test message that is long enough", | |
| proposedTopics: [], | |
| proposedDates: [pastDate], | |
| }, "test-user-123") | |
| ).rejects.toThrow(BadRequestException); | |
| }); | |
| }); | |
| // βββ Booking Request Response ββββββββββββββββββββββββ | |
| describe("respondToBookingRequest", () => { | |
| it("should reject response to expired request (older than 30 days)", async () => { | |
| const thirtyOneDaysAgo = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000); | |
| mockPrisma.networkBookingRequest.findUnique.mockResolvedValue({ | |
| id: "req-1", | |
| status: "pending", | |
| createdAt: thirtyOneDaysAgo, | |
| toProfile: { userId: "user-1" }, | |
| fromPodcast: { id: "podcast-1", title: "Test Podcast", workspaceId: "ws-1" }, | |
| }); | |
| mockPrisma.networkBookingRequest.update.mockResolvedValue({ | |
| id: "req-1", | |
| status: "expired", | |
| }); | |
| await expect( | |
| service.respondToBookingRequest("req-1", "user-1", "accepted") | |
| ).rejects.toThrow(ConflictException); | |
| // Verify it was expired in the DB | |
| expect(mockPrisma.networkBookingRequest.update).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| data: expect.objectContaining({ status: "expired" }), | |
| }) | |
| ); | |
| }); | |
| it("should reject response from wrong user", async () => { | |
| mockPrisma.networkBookingRequest.findUnique.mockResolvedValue({ | |
| id: "req-1", | |
| status: "pending", | |
| createdAt: new Date(), | |
| toProfile: { userId: "profile-owner" }, | |
| fromPodcast: { id: "podcast-1", title: "Test Podcast", workspaceId: "ws-1" }, | |
| }); | |
| await expect( | |
| service.respondToBookingRequest("req-1", "wrong-user", "accepted") | |
| ).rejects.toThrow(ForbiddenException); | |
| }); | |
| it("should reject response to already-responded request", async () => { | |
| mockPrisma.networkBookingRequest.findUnique.mockResolvedValue({ | |
| id: "req-1", | |
| status: "accepted", | |
| createdAt: new Date(), | |
| toProfile: { userId: "user-1" }, | |
| fromPodcast: { id: "podcast-1", title: "Test Podcast", workspaceId: "ws-1" }, | |
| }); | |
| await expect( | |
| service.respondToBookingRequest("req-1", "user-1", "declined") | |
| ).rejects.toThrow(ConflictException); | |
| }); | |
| }); | |
| // βββ Host Review Authorization βββββββββββββββββββββββ | |
| describe("submitReview β host authorization", () => { | |
| it("should reject a review when the caller is not a member of the host podcast", async () => { | |
| mockWorkspaceMember.findFirst.mockResolvedValue(null); | |
| mockPrisma.networkBookingRequest.findUnique.mockResolvedValue({ | |
| id: "req-1", | |
| status: "accepted", | |
| toProfileId: "profile-1", | |
| fromPodcast: { id: "podcast-1", workspaceId: "ws-1" }, | |
| }); | |
| mockPrisma.hostReview.findUnique.mockResolvedValue(null); | |
| await expect( | |
| service.submitReview("tester", { | |
| bookingRequestId: "req-1", | |
| profileId: "profile-1", | |
| contentRating: 5, | |
| prepQuality: 4, | |
| overallRating: 5, | |
| comment: "Great guest!", | |
| }) | |
| ).rejects.toThrow(ForbiddenException); | |
| }); | |
| it("should allow a review when the caller is a member of the host podcast", async () => { | |
| mockWorkspaceMember.findFirst.mockResolvedValue({ id: "wm-1" }); | |
| mockPrisma.networkBookingRequest.findUnique.mockResolvedValue({ | |
| id: "req-1", | |
| status: "accepted", | |
| toProfileId: "profile-1", | |
| fromPodcast: { id: "podcast-1", workspaceId: "ws-1" }, | |
| }); | |
| mockPrisma.hostReview.findUnique.mockResolvedValue(null); | |
| mockPrisma.hostReview.create.mockResolvedValue({ | |
| id: "review-1", | |
| bookingRequestId: "req-1", | |
| profileId: "profile-1", | |
| }); | |
| const result = await service.submitReview("tester", { | |
| bookingRequestId: "req-1", | |
| profileId: "profile-1", | |
| contentRating: 5, | |
| prepQuality: 4, | |
| overallRating: 5, | |
| comment: "Great guest!", | |
| }); | |
| expect(result.id).toBe("review-1"); | |
| expect(mockPrisma.hostReview.create).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| data: expect.objectContaining({ | |
| bookingRequestId: "req-1", | |
| profileId: "profile-1", | |
| }), | |
| }) | |
| ); | |
| }); | |
| }); | |
| // βββ Duplicate Review Prevention βββββββββββββββββββββ | |
| describe("submitReview β duplicate prevention", () => { | |
| it("should reject duplicate review for the same booking", async () => { | |
| mockPrisma.networkBookingRequest.findUnique.mockResolvedValue({ | |
| id: "req-1", | |
| status: "accepted", | |
| toProfileId: "profile-1", | |
| }); | |
| mockPrisma.hostReview.findUnique.mockResolvedValue({ | |
| id: "existing-review", | |
| bookingRequestId: "req-1", | |
| }); | |
| await expect( | |
| service.submitReview("test-user", { | |
| bookingRequestId: "req-1", | |
| profileId: "profile-1", | |
| contentRating: 5, | |
| prepQuality: 4, | |
| overallRating: 5, | |
| comment: "Great guest!", | |
| }) | |
| ).rejects.toThrow(ConflictException); | |
| }); | |
| it("should reject review with mismatched profileId", async () => { | |
| mockPrisma.networkBookingRequest.findUnique.mockResolvedValue({ | |
| id: "req-1", | |
| status: "accepted", | |
| toProfileId: "profile-correct", | |
| }); | |
| mockPrisma.hostReview.findUnique.mockResolvedValue(null); | |
| await expect( | |
| service.submitReview("test-user", { | |
| bookingRequestId: "req-1", | |
| profileId: "profile-wrong", | |
| contentRating: 5, | |
| prepQuality: 4, | |
| overallRating: 5, | |
| comment: "Great guest!", | |
| }) | |
| ).rejects.toThrow(BadRequestException); | |
| }); | |
| }); // βββ Profile View Rate Limiting ββββββββββββββββββββββ | |
| describe("trackView β rate limiting", () => { | |
| it("should deduplicate views from same podcast within 24h", async () => { | |
| mockRateLimiter.checkLimit.mockResolvedValue({ allowed: true, remaining: 9, resetAt: new Date() }); | |
| mockPrisma.profileView.findFirst.mockResolvedValue({ | |
| id: "view-1", | |
| profileId: "profile-1", | |
| viewerPodcastId: "podcast-1", | |
| }); | |
| mockPrisma.profileView.update.mockResolvedValue({ | |
| id: "view-1", | |
| }); | |
| const result = await service.trackView({ | |
| profileId: "profile-1", | |
| viewerPodcastId: "podcast-1", | |
| source: "search" as any, | |
| }); | |
| expect(result).toHaveProperty("deduplicated", true); | |
| // Should NOT create a new view | |
| expect(mockPrisma.profileView.create).not.toHaveBeenCalled(); | |
| }); | |
| }); | |
| }); | |