Files
picpeak/tests/e2e/gallery-feedback-filter.spec.ts
T

227 lines
7.1 KiB
TypeScript

import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import fs from 'fs';
import path from 'path';
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@example.com';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'Admin!234';
const GALLERY_PASSWORD = process.env.GALLERY_PASSWORD || 'PlaywrightGallery123!';
interface GallerySetupResult {
shareLink: string;
slug: string;
allPhotosData: {
event: any;
categories?: any;
photos: Array<{ id: number; filename: string; comment_count?: number }>;
};
}
async function createGalleryWithModeratedComments(page: Page): Promise<GallerySetupResult> {
const loginResponse = await page.request.post('/api/auth/admin/login', {
data: {
username: ADMIN_EMAIL,
password: ADMIN_PASSWORD,
},
failOnStatusCode: false,
});
expect(loginResponse.ok()).toBeTruthy();
const { token } = await loginResponse.json();
expect(token).toBeTruthy();
const eventName = `Playwright Feedback Filter ${Date.now()}`;
const eventDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
.toISOString()
.slice(0, 10);
const createResponse = await page.request.post('/api/admin/events', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
data: {
event_type: 'wedding',
event_name: eventName,
event_date: eventDate,
customer_name: 'Playwright Host',
customer_email: 'host@example.com',
admin_email: ADMIN_EMAIL,
password: GALLERY_PASSWORD,
expiration_days: 30,
allow_user_uploads: false,
allow_downloads: true,
disable_right_click: false,
watermark_downloads: false,
feedback_enabled: true,
allow_ratings: true,
allow_likes: true,
allow_comments: true,
allow_favorites: true,
require_name_email: false,
moderate_comments: true,
show_feedback_to_guests: true,
},
failOnStatusCode: false,
});
expect(createResponse.ok()).toBeTruthy();
const createdEvent = await createResponse.json();
expect(createdEvent?.id).toBeTruthy();
const imagePaths = ['img1.png', 'img2.png'];
const photoIds: number[] = [];
for (const file of imagePaths) {
const imagePath = path.join(process.cwd(), 'test-assets', file);
const buffer = fs.readFileSync(imagePath);
const uploadResponse = await page.request.post(
`/api/admin/events/${createdEvent.id}/upload`,
{
headers: {
Authorization: `Bearer ${token}`,
},
multipart: {
photos: {
name: path.basename(imagePath),
mimeType: 'image/png',
buffer,
},
category_id: 'individual',
},
failOnStatusCode: false,
}
);
expect(uploadResponse.ok()).toBeTruthy();
const uploadJson = await uploadResponse.json();
const uploaded = uploadJson?.photos?.[0];
expect(uploaded?.id).toBeTruthy();
photoIds.push(uploaded.id);
}
expect(photoIds.length).toBeGreaterThanOrEqual(2);
const galleryAuthResponse = await page.request.post('/api/auth/gallery/verify', {
data: {
slug: createdEvent.slug,
password: GALLERY_PASSWORD,
},
failOnStatusCode: false,
});
expect(galleryAuthResponse.ok()).toBeTruthy();
const { token: galleryToken } = await galleryAuthResponse.json();
expect(galleryToken).toBeTruthy();
// Submit an approved comment (after moderation)
const approvedCommentResponse = await page.request.post(
`/api/gallery/${createdEvent.slug}/photos/${photoIds[0]}/feedback`,
{
headers: {
Authorization: `Bearer ${galleryToken}`,
'Content-Type': 'application/json',
},
data: {
feedback_type: 'comment',
comment_text: 'Approved comment',
guest_name: 'Approved Guest',
guest_email: 'approved@example.com',
},
failOnStatusCode: false,
}
);
expect(approvedCommentResponse.ok()).toBeTruthy();
const approvedComment = await approvedCommentResponse.json();
expect(approvedComment?.id).toBeTruthy();
const approveModeration = await page.request.put(
`/api/admin/feedback/feedback/${approvedComment.id}/approve`,
{
headers: {
Authorization: `Bearer ${token}`,
},
failOnStatusCode: false,
}
);
expect(approveModeration.ok()).toBeTruthy();
// Submit a second comment that remains pending
const pendingCommentResponse = await page.request.post(
`/api/gallery/${createdEvent.slug}/photos/${photoIds[1]}/feedback`,
{
headers: {
Authorization: `Bearer ${galleryToken}`,
'Content-Type': 'application/json',
},
data: {
feedback_type: 'comment',
comment_text: 'Pending comment',
guest_name: 'Pending Guest',
guest_email: 'pending@example.com',
},
failOnStatusCode: false,
}
);
expect(pendingCommentResponse.ok()).toBeTruthy();
const allPhotosResponse = await page.request.get(`/api/gallery/${createdEvent.slug}/photos`, {
headers: {
Authorization: `Bearer ${galleryToken}`,
},
failOnStatusCode: false,
});
expect(allPhotosResponse.ok()).toBeTruthy();
const allPhotosData = await allPhotosResponse.json();
expect(Array.isArray(allPhotosData?.photos)).toBeTruthy();
return {
shareLink: createdEvent.share_link,
slug: createdEvent.slug,
allPhotosData,
};
}
test.describe('Gallery feedback filter', () => {
test('Comment filter hides photos without approved comments', async ({ page }) => {
const { shareLink, slug, allPhotosData } = await createGalleryWithModeratedComments(page);
const approvedPhotos = allPhotosData.photos.filter((photo) => (photo.comment_count || 0) > 0);
expect(approvedPhotos.length).toBeGreaterThan(0);
await page.route(`**/api/gallery/${slug}/photos**`, async (route) => {
const url = new URL(route.request().url());
if (url.searchParams.get('filter') === 'commented') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(allPhotosData),
});
await page.unroute(`**/api/gallery/${slug}/photos**`);
} else {
await route.continue();
}
});
await page.goto(shareLink);
await page.waitForLoadState('domcontentloaded');
const passwordField = page.getByPlaceholder(/gallery password/i).first();
if (await passwordField.count()) {
await passwordField.fill(GALLERY_PASSWORD);
await page.getByRole('button', { name: /View Gallery/i }).click();
}
await page.waitForLoadState('networkidle');
const tiles = page.locator('.relative.group');
await expect(tiles.first()).toBeVisible({ timeout: 20000 });
await expect(tiles).toHaveCount(allPhotosData.photos.length);
await page.getByRole('button', { name: /Commented/i }).click();
await expect(tiles).toHaveCount(approvedPhotos.length, { timeout: 20000 });
for (const pending of allPhotosData.photos.filter((photo) => (photo.comment_count || 0) === 0)) {
await expect(page.getByAltText(pending.filename)).not.toBeVisible({ timeout: 1000 });
}
});
});