import { TestBed } from '@angular/core/testing'; import { of, throwError } from 'rxjs'; import { AdminModerationFacade } from './admin-moderation.facade'; import { ADMIN_MODERATION_GATEWAY } from '../services/admin-moderation-gateway.token'; import { AdminModerationGateway } from '../services/admin-moderation-gateway.interface'; import { AdminReview } from '../models/admin-review.model'; import { LocalStorageService } from '../../../../core/storage/local-storage.service'; function review(overrides: Partial = {}): AdminReview { return { id: 'r1', productId: 'p1', productName: 'Widget', customerName: 'Buyer', customerEmail: 'b@example.com', rating: 5, text: 'Great', photos: [], status: 'pending', visible: true, pinned: false, featured: false, reportCount: 0, moderatorNotes: '', timeline: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), ...overrides, }; } describe('AdminModerationFacade', () => { let facade: AdminModerationFacade; let gateway: jasmine.SpyObj; beforeEach(() => { gateway = jasmine.createSpyObj('AdminModerationGateway', [ 'loadReviews', 'loadReview', 'setReviewStatus', 'setReviewVisible', 'setReviewPinned', 'setReviewFeatured', 'addModeratorNote', 'deleteReview', 'loadReports', 'setReportStatus', ]); TestBed.configureTestingModule({ providers: [ { provide: ADMIN_MODERATION_GATEWAY, useValue: gateway }, { provide: LocalStorageService, useValue: { getItem: () => null, setItem: () => {}, getJSON: () => null, setJSON: () => {} } }, ], }); facade = TestBed.inject(AdminModerationFacade); }); describe('health', () => { it('scores 100% when every check passes', () => { const result = facade.health(review({ rating: 5, text: 'x', photos: ['a'], status: 'approved', reportCount: 0, visible: true })); expect(result.completionPercent).toBe(100); }); it('scores 0% when every check fails', () => { const result = facade.health(review({ rating: 0, text: '', photos: [], status: 'pending', reportCount: 1, visible: false })); expect(result.completionPercent).toBe(0); }); it('treats whitespace-only text as no text', () => { const result = facade.health(review({ text: ' ' })); expect(result.hasText).toBeFalse(); }); it('any status other than pending counts as moderated', () => { expect(facade.health(review({ status: 'approved' })).isModerated).toBeTrue(); expect(facade.health(review({ status: 'rejected' })).isModerated).toBeTrue(); expect(facade.health(review({ status: 'pending' })).isModerated).toBeFalse(); }); }); describe('loadDashboardStats', () => { it('computes averageRating only from reviews that have a rating', () => { gateway.loadReviews.and.returnValue(of({ items: [review({ rating: 4 }), review({ rating: 0 }), review({ rating: 5 })], total: 3, page: 1, pageSize: 100000, })); facade.loadDashboardStats(); // (4 + 5) / 2 = 4.5 - the zero-rating review must not drag the average down. expect(facade.dashboardStats()?.averageRating).toBe(4.5); }); it('averageRating is null when no review has a rating, not 0', () => { gateway.loadReviews.and.returnValue(of({ items: [review({ rating: 0 })], total: 1, page: 1, pageSize: 100000, })); facade.loadDashboardStats(); expect(facade.dashboardStats()?.averageRating).toBeNull(); }); it('moderationHealthPercent is 100 for an empty queue, not a divide-by-zero artifact', () => { gateway.loadReviews.and.returnValue(of({ items: [], total: 0, page: 1, pageSize: 100000 })); facade.loadDashboardStats(); expect(facade.dashboardStats()?.moderationHealthPercent).toBe(100); }); it('counts reported reviews independently of their moderation status', () => { gateway.loadReviews.and.returnValue(of({ items: [review({ status: 'approved', reportCount: 2 }), review({ status: 'pending', reportCount: 0 })], total: 2, page: 1, pageSize: 100000, })); facade.loadDashboardStats(); expect(facade.dashboardStats()?.reported).toBe(1); }); }); describe('setReportStatus', () => { it('reloads the reports list after a successful status change', () => { gateway.setReportStatus.and.returnValue(of(null)); gateway.loadReports.and.returnValue(of([])); facade.setReportStatus('rep1', 'resolved'); expect(gateway.loadReports).toHaveBeenCalled(); }); }); describe('loadReports error handling', () => { it('clears reports and sets reportsError on failure', () => { gateway.loadReports.and.returnValue(throwError(() => new Error('x'))); facade.loadReports(); expect(facade.reports()).toEqual([]); expect(facade.reportsError()).toBeTrue(); expect(facade.reportsLoading()).toBeFalse(); }); }); describe('selection', () => { it('toggleAll selects every loaded review id', () => { gateway.loadReviews.and.returnValue(of({ items: [review({ id: 'a' }), review({ id: 'b' })], total: 2, page: 1, pageSize: 10 })); facade.loadList(); facade.toggleAll(true); expect(facade.selectedIds()).toEqual(['a', 'b']); }); }); });