Add short gallery URL toggle and token support (#38)
This commit is contained in:
+139
-39
@@ -6,23 +6,32 @@ 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!';
|
||||
|
||||
async function createEventWithPhotos(page: Page) {
|
||||
async function createEventWithPhotos(page: Page, adminToken?: string, attempt = 1) {
|
||||
const api = page.request;
|
||||
const loginResponse = await api.post('/api/auth/admin/login', {
|
||||
data: {
|
||||
username: ADMIN_EMAIL,
|
||||
password: ADMIN_PASSWORD,
|
||||
},
|
||||
});
|
||||
expect(loginResponse.ok()).toBeTruthy();
|
||||
const { token } = await loginResponse.json();
|
||||
expect(token).toBeTruthy();
|
||||
let token = adminToken;
|
||||
|
||||
if (!token) {
|
||||
const loginResponse = await api.post('/api/auth/admin/login', {
|
||||
data: {
|
||||
username: ADMIN_EMAIL,
|
||||
password: ADMIN_PASSWORD,
|
||||
},
|
||||
});
|
||||
expect(loginResponse.ok()).toBeTruthy();
|
||||
const loginData = await loginResponse.json();
|
||||
token = loginData.token;
|
||||
expect(token).toBeTruthy();
|
||||
}
|
||||
|
||||
const eventName = `Playwright Smoke ${Date.now()}`;
|
||||
const eventDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
||||
.toISOString()
|
||||
.slice(0, 10);
|
||||
|
||||
if (!token) {
|
||||
throw new Error('Failed to acquire admin token');
|
||||
}
|
||||
|
||||
const eventResponse = await api.post('/api/admin/events', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
@@ -34,6 +43,8 @@ async function createEventWithPhotos(page: Page) {
|
||||
event_date: eventDate,
|
||||
customer_name: 'Playwright Host',
|
||||
customer_email: 'host@example.com',
|
||||
host_name: 'Playwright Host',
|
||||
host_email: 'host@example.com',
|
||||
admin_email: ADMIN_EMAIL,
|
||||
password: GALLERY_PASSWORD,
|
||||
expiration_days: 30,
|
||||
@@ -43,7 +54,17 @@ async function createEventWithPhotos(page: Page) {
|
||||
watermark_downloads: false,
|
||||
},
|
||||
});
|
||||
expect(eventResponse.ok()).toBeTruthy();
|
||||
if (!eventResponse.ok()) {
|
||||
const message = await eventResponse.text();
|
||||
if (
|
||||
attempt < 3 &&
|
||||
/UNIQUE constraint failed: events\.slug/i.test(message || '')
|
||||
) {
|
||||
await page.waitForTimeout(150);
|
||||
return createEventWithPhotos(page, token, attempt + 1);
|
||||
}
|
||||
throw new Error(`Event creation failed: ${eventResponse.status()} ${message}`);
|
||||
}
|
||||
const event = await eventResponse.json();
|
||||
|
||||
const imagePath = path.join(process.cwd(), 'test-assets', 'img1.png');
|
||||
@@ -67,11 +88,83 @@ async function createEventWithPhotos(page: Page) {
|
||||
event,
|
||||
shareLink: event.share_link,
|
||||
slug: event.slug,
|
||||
adminToken: token,
|
||||
};
|
||||
}
|
||||
|
||||
async function updateShortGallerySetting(page: Page, adminToken: string, enabled: boolean) {
|
||||
const response = await page.request.put('/api/admin/settings/general', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${adminToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
general_short_gallery_urls: enabled,
|
||||
},
|
||||
});
|
||||
expect(response.ok()).toBeTruthy();
|
||||
}
|
||||
|
||||
async function openGalleryShareLink(page: Page, shareLink: string) {
|
||||
await page.context().clearCookies();
|
||||
await page.goto(shareLink);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
try {
|
||||
await page.getByText(/Enter Gallery Password/i).first().waitFor({ timeout: 5000 });
|
||||
} catch {
|
||||
// No password prompt shown (public gallery)
|
||||
}
|
||||
|
||||
let passwordEntered = false;
|
||||
const passwordTextbox = page.getByRole('textbox', { name: /password/i }).first();
|
||||
if (await passwordTextbox.count()) {
|
||||
await passwordTextbox.fill(GALLERY_PASSWORD);
|
||||
passwordEntered = true;
|
||||
}
|
||||
|
||||
const galleryPasswordField = page.getByPlaceholder(/gallery password/i);
|
||||
if (!passwordEntered && await galleryPasswordField.count()) {
|
||||
await galleryPasswordField.fill(GALLERY_PASSWORD);
|
||||
passwordEntered = true;
|
||||
} else if (!passwordEntered) {
|
||||
const genericPasswordField = page.getByPlaceholder(/password/i).first();
|
||||
if (await genericPasswordField.count()) {
|
||||
await genericPasswordField.fill(GALLERY_PASSWORD);
|
||||
passwordEntered = true;
|
||||
} else {
|
||||
const labelledPasswordField = page.getByLabel(/password/i).first();
|
||||
if (await labelledPasswordField.count()) {
|
||||
await labelledPasswordField.fill(GALLERY_PASSWORD);
|
||||
passwordEntered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!passwordEntered) {
|
||||
const fallbackPasswordField = page.locator('input').first();
|
||||
if (await fallbackPasswordField.count()) {
|
||||
await fallbackPasswordField.fill(GALLERY_PASSWORD);
|
||||
passwordEntered = true;
|
||||
}
|
||||
}
|
||||
|
||||
const viewButton = page.getByRole('button', { name: /View Gallery/i });
|
||||
if (await viewButton.count()) {
|
||||
try {
|
||||
await viewButton.click({ noWaitAfter: true, timeout: 2000 });
|
||||
} catch {
|
||||
// Already navigated into gallery view.
|
||||
}
|
||||
}
|
||||
|
||||
const tiles = page.locator('.relative.group');
|
||||
await expect(tiles.first()).toBeVisible({ timeout: 20000 });
|
||||
return tiles;
|
||||
}
|
||||
|
||||
test('admin login and gallery viewing smoke test', async ({ page }) => {
|
||||
const { shareLink } = await createEventWithPhotos(page);
|
||||
const { shareLink, adminToken } = await createEventWithPhotos(page);
|
||||
|
||||
// Admin UI login
|
||||
await page.goto('/admin/login');
|
||||
@@ -83,32 +176,39 @@ test('admin login and gallery viewing smoke test', async ({ page }) => {
|
||||
}
|
||||
await expect(page.getByRole('heading', { name: /Dashboard/i })).toBeVisible({ timeout: 20000 });
|
||||
|
||||
// Visit gallery share link and authenticate
|
||||
await page.goto(shareLink);
|
||||
const passwordField = page.getByPlaceholder(/gallery password/i);
|
||||
if (await passwordField.count()) {
|
||||
try {
|
||||
await passwordField.fill(GALLERY_PASSWORD, { timeout: 2000 });
|
||||
} catch {
|
||||
// Field may disappear if gallery bypasses password; ignore.
|
||||
}
|
||||
let resetToken = adminToken;
|
||||
try {
|
||||
// Verify long-form share link works
|
||||
const tiles = await openGalleryShareLink(page, shareLink);
|
||||
await tiles.first().hover();
|
||||
await tiles.first().getByRole('button', { name: /View full size/i }).click();
|
||||
await expect(page.getByRole('button', { name: /Close/i })).toBeVisible();
|
||||
await page.getByRole('button', { name: /Close/i }).click();
|
||||
|
||||
// Enable short gallery URLs
|
||||
await updateShortGallerySetting(page, adminToken, true);
|
||||
|
||||
const settingsResponse = await page.request.get('/api/admin/settings', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${adminToken}`,
|
||||
},
|
||||
});
|
||||
expect(settingsResponse.ok()).toBeTruthy();
|
||||
const adminSettings = await settingsResponse.json();
|
||||
expect(adminSettings.general_short_gallery_urls === true || adminSettings.general_short_gallery_urls === 'true').toBeTruthy();
|
||||
|
||||
const { shareLink: shortShareLink, event: shortEvent } = await createEventWithPhotos(page, adminToken);
|
||||
expect(shortShareLink).toMatch(/\/gallery\/[0-9a-fA-F]{32}$/);
|
||||
expect(shortShareLink).not.toContain(shortEvent.slug);
|
||||
|
||||
// Verify short share link works
|
||||
await openGalleryShareLink(page, shortShareLink);
|
||||
|
||||
// Legacy share link should still work after enabling short URLs
|
||||
await openGalleryShareLink(page, shareLink);
|
||||
} finally {
|
||||
await updateShortGallerySetting(page, resetToken, false).catch(() => {
|
||||
/* noop */
|
||||
});
|
||||
}
|
||||
|
||||
const viewButton = page.getByRole('button', { name: /View Gallery/i });
|
||||
if (await viewButton.count()) {
|
||||
try {
|
||||
await viewButton.click({ noWaitAfter: true, timeout: 2000 });
|
||||
} catch {
|
||||
// Already inside gallery view.
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for photos grid to appear
|
||||
const tiles = page.locator('.relative.group');
|
||||
await expect(tiles.first()).toBeVisible({ timeout: 20000 });
|
||||
|
||||
// Open lightbox to ensure media renders
|
||||
await tiles.first().hover();
|
||||
await tiles.first().getByRole('button', { name: /View full size/i }).click();
|
||||
await expect(page.getByRole('button', { name: /Close/i })).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user