Add Grizzlyflix newsletters with curated editions and weekly delivery
Magent CI/CD / verify (push) Successful in 10m54s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 1m14s

This commit is contained in:
2026-09-09 23:42:52 +12:00
parent e014baadc3
commit b0f8c89db7
22 changed files with 2223 additions and 21 deletions
+216
View File
@@ -0,0 +1,216 @@
// Fixture-only review: every API request is intercepted, including all send actions.
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const { chromium } = require(process.env.REVIEW_PLAYWRIGHT || 'playwright')
const base = process.env.REVIEW_BASE || 'http://localhost:3114'
const output = process.env.REVIEW_DIR
const preview = JSON.parse(fs.readFileSync(process.env.REVIEW_EMAIL_FIXTURE, 'utf8'))
const movie = '1'.repeat(32), series = '2'.repeat(32), editionId = 'e'.repeat(32)
const clone = (value) => JSON.parse(JSON.stringify(value))
const titles = [{ id: movie, title: 'Arrival', type: 'movie', year: 2016, has_artwork: true, items: [{ id: movie }], selected: true, featured: false }, { id: series, title: 'Severance', type: 'series', year: null, has_artwork: true, items: [{ id: '3'.repeat(32), season: 2 }, { id: '4'.repeat(32), season: 2 }], selected: true, featured: false }]
const draft = () => ({ id: editionId, subject: 'Weekend discoveries', intro: '', revision: 1, state: 'draft', origin: 'manual', send_at: null, created_at: Date.now()/1000, content: { titles: clone(titles), total_titles: 2, period_start: '2026-09-04T09:00:00+00:00', period_end: '2026-09-11T09:00:00+00:00' } })
;(async () => {
const browser = await chromium.launch({ headless: true })
try {
const context = await browser.newContext()
await context.addCookies([{ name: 'magent_logged_in', value: '1', url: base }])
const calls = []
let role = 'admin', failSave = false, failPreview = false, email = 'viewer@example.test', tokenState = 'ready'
let heldConfirmation = null
let settings = { enabled: false, weekday: 4, hour: 9, limit_titles: 12, public_url: 'https://beta.example.test', intro: '', revision: 1, next_send_at: null }
let preference = { state: 'off', email, can_subscribe: true, detail: 'Ready', schedule_enabled: false, weekday: 4, hour: 9, next_send_at: null, resend_after: null }
let edition = null, deliveries = []
const overview = () => ({ settings, ready: true, detail: 'Ready', subscribers: 12, editions: edition ? [{ ...edition, titles: edition.content.titles.filter((title) => title.selected).length, content: undefined, period_start: edition.content.period_start, period_end: edition.content.period_end }] : [], deliveries, total: deliveries.length })
await context.route('**/api/**', async (route) => {
const request = route.request(), url = new URL(request.url()), payload = request.postDataJSON()
calls.push({ method: request.method(), path: url.pathname, payload })
if (url.pathname === '/api/auth/me') return route.fulfill({ json: { username: 'Fixture viewer', role, email } })
if (url.pathname === '/api/auth/profile') return route.fulfill({ json: { user: { username: 'Fixture viewer', role, email, auth_provider: 'jellyfin', password_change_supported: true, password_provider: 'jellyfin' }, activity: { recent: [] } } })
if (url.pathname === '/api/auth/profile/email') { email = payload.email; preference = { ...preference, email, state: 'off' }; return route.fulfill({ json: { email } }) }
if (url.pathname === '/api/profile/email-recaps') return route.fulfill({ json: { ...preference, state: 'enabled', day: 2, resend_after: null } })
if (url.pathname === '/api/profile/newsletters') {
if (request.method() === 'PUT') preference = { ...preference, state: payload.enabled ? 'pending' : 'off', resend_after: payload.enabled ? Date.now()/1000+300 : null }
return route.fulfill({ json: preference })
}
if (url.pathname.startsWith('/api/newsletter-subscription/')) {
if (url.pathname.endsWith('/confirm')) {
if (heldConfirmation) { heldConfirmation.started(); await heldConfirmation.released }
tokenState = payload.action === 'confirm' ? 'enabled' : 'off'
}
return route.fulfill({ json: { action: payload.action, state: tokenState } })
}
if (url.pathname.startsWith('/api/admin/newsletters')) {
if (role !== 'admin') return route.fulfill({ status: role === 'unauthorized' ? 401 : 403, json: { detail: 'Administrator access is required.' } })
if (url.pathname.includes('/artwork/')) return route.fulfill({ contentType: 'image/svg+xml', body: '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="150"><rect width="100" height="150" fill="#655183"/><circle cx="50" cy="65" r="28" fill="#c7bdff"/></svg>' })
if (url.pathname.endsWith('/drafts')) { assert([7, 14, 30].includes(payload.days)); edition = draft(); return route.fulfill({ status: 201, json: edition }) }
if (url.pathname.endsWith('/preview')) {
assert.equal(payload.revision, edition.revision)
if (failPreview) return route.fulfill({ status: 502, json: { detail: 'Jellyfin is temporarily unavailable. Please try again.' } })
return route.fulfill({ json: { ...preview, id: edition.id, revision: edition.revision, subject: edition.subject } })
}
if (url.pathname.endsWith('/test')) {
assert.deepEqual(Object.keys(payload).sort(), ['request_id', 'revision'])
deliveries = [{ id: payload.request_id, subject: edition.subject, username: 'Fixture viewer', email, kind: 'test', state: 'queued', attempts: 0, updated_at: Date.now()/1000, detail: '' }]
return route.fulfill({ status: 202, json: { id: payload.request_id, message: 'Test queued for your confirmed newsletter email.' } })
}
if (url.pathname.endsWith('/publish')) { assert.equal(payload.revision, edition.revision); edition = { ...edition, state: 'scheduled', send_at: payload.send_at ? Date.parse(payload.send_at)/1000 : Date.now()/1000 }; return route.fulfill({ status: 202, json: edition }) }
if (url.pathname.endsWith('/cancel')) { edition = { ...edition, state: 'cancelled' }; return route.fulfill({ json: edition }) }
if (url.pathname.includes('/editions/')) {
if (request.method() === 'PUT') {
if (failSave) return route.fulfill({ status: 409, json: { detail: 'This edition changed. Reload it before continuing.' } })
assert.equal(payload.revision, edition.revision)
assert.deepEqual(Object.keys(payload.titles[0]).sort(), ['featured', 'id', 'selected'])
edition = { ...edition, subject: payload.subject, intro: payload.intro, revision: edition.revision+1, content: { ...edition.content, titles: edition.content.titles.map((title) => ({ ...title, ...payload.titles.find((selection) => selection.id === title.id) })) } }
}
return route.fulfill({ json: edition })
}
if (request.method() === 'PUT') { settings = { ...payload, revision: payload.revision+1, next_send_at: payload.enabled ? Date.now()/1000+86400 : null }; return route.fulfill({ json: settings }) }
return route.fulfill({ json: overview() })
}
if (url.pathname.includes('/events/stream')) return route.fulfill({ contentType: 'text/event-stream', body: ': fixture\n\n' })
return route.fulfill({ json: {} })
})
const page = await context.newPage(), errors = []
page.on('pageerror', (error) => errors.push(error.message))
if (output) fs.mkdirSync(output, { recursive: true })
for (const width of [1440, 980, 390, 320]) {
await page.setViewportSize({ width, height: 1000 })
await page.goto(`${base}/admin/newsletters`)
await page.getByRole('button', { name: 'Create draft', exact: true }).click()
await page.getByLabel('Email subject', { exact: true }).waitFor()
assert.equal(await page.getByRole('button', { name: 'Send newsletter test to me' }).isDisabled(), true)
await page.getByLabel('Feature Arrival', { exact: true }).check()
await page.getByLabel('Announcement', { exact: false }).fill('A weekend of good stories.')
assert.equal(await page.getByRole('button', { name: 'Preview edition', exact: true }).isDisabled(), true)
await page.getByRole('button', { name: 'Save draft', exact: true }).click()
await page.getByRole('button', { name: 'Preview edition', exact: true }).click()
const frame = page.frameLocator('iframe[title="Newsletter email preview"]')
await frame.getByRole('heading', { name: 'Whats new on Grizzlyflix', exact: true }).waitFor()
assert.equal(await page.locator('iframe').getAttribute('sandbox'), '')
assert.equal(await frame.locator('img').count(), 2)
assert(await frame.locator('img').evaluateAll((images) => images.every((image) => image.complete && image.naturalWidth > 0)), 'Email posters did not load')
assert.equal(await frame.getByRole('link', { name: 'Watch on Grizzlyflix ↗', exact: true }).first().getAttribute('href'), `https://watch.example.test/web/index.html#!/details?id=${movie}&serverId=${'b'.repeat(32)}`)
assert(await page.locator('.newsletter-poster img').evaluateAll((images) => images.every((image) => image.complete && image.naturalWidth > 0)), 'Editor posters did not load')
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Editor overflow at ${width}`)
assert(await frame.locator('body').evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Email overflow at ${width}`)
if (output) {
await page.locator('iframe').scrollIntoViewIfNeeded()
await page.locator('iframe').screenshot({ path: path.join(output, `newsletter-email-${width}.png`) })
await page.evaluate(() => window.scrollTo(0, 0))
await page.screenshot({ path: path.join(output, `newsletter-editor-${width}.png`), fullPage: true })
}
await page.getByRole('button', { name: 'Plain text', exact: true }).click()
assert.match(await page.locator('.recap-plain-preview').innerText(), /2 new episodes/)
await page.getByRole('button', { name: 'Weekly schedule', exact: true }).click()
await page.getByRole('heading', { name: 'A weekly discovery', exact: true }).waitFor()
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Schedule overflow at ${width}`)
if (output) await page.screenshot({ path: path.join(output, `newsletter-schedule-${width}.png`), fullPage: true })
await page.goto(`${base}/profile#newsletters`)
await page.getByRole('heading', { name: 'New on Grizzlyflix.', exact: true }).waitFor()
assert.equal(await page.locator('#monthly-recaps').getByText('Subscribed', { exact: true }).count(), 1)
assert.equal(await page.locator('#newsletters').getByText('Off', { exact: true }).count(), 1)
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Profile overflow at ${width}`)
if (output) await page.locator('#newsletters').screenshot({ path: path.join(output, `newsletter-profile-${width}.png`) })
}
await page.getByRole('button', { name: 'Email me new arrivals', exact: true }).click()
await page.locator('#newsletters').getByText('Check your inbox', { exact: true }).waitFor()
assert.equal(await page.getByRole('button', { name: 'Resend newsletter confirmation' }).isDisabled(), true)
await page.getByRole('button', { name: 'Cancel newsletter subscription' }).click()
await page.locator('#newsletters').getByText('Off', { exact: true }).waitFor()
preference = { ...preference, state: 'enabled' }
await page.getByRole('button', { name: 'Refresh newsletter preference' }).click()
await page.locator('#newsletters').getByText('Subscribed', { exact: true }).waitFor()
await page.getByRole('button', { name: 'Turn off newsletters' }).click()
await page.locator('#newsletters').getByText('Off', { exact: true }).waitFor()
assert.equal(await page.locator('#monthly-recaps').getByText('Subscribed', { exact: true }).count(), 1)
assert.equal(calls.filter((call) => call.path === '/api/profile/email-recaps' && call.method === 'PUT').length, 0)
await page.goto(`${base}/admin/newsletters`)
await page.locator('.newsletter-edition').first().click()
await page.getByLabel('Email subject').fill('Unsaved subject')
failSave = true
await page.getByRole('button', { name: 'Save draft', exact: true }).click()
await page.getByRole('alert').filter({ hasText: 'This edition changed' }).waitFor()
assert.equal(await page.getByRole('button', { name: 'Preview edition', exact: true }).isDisabled(), true)
failSave = false
await page.getByRole('button', { name: 'Discard changes', exact: true }).click()
await page.getByRole('button', { name: 'Reload edition', exact: true }).waitFor()
failPreview = true
await page.getByRole('button', { name: 'Preview edition', exact: true }).click()
await page.getByRole('alert').filter({ hasText: 'Jellyfin is temporarily unavailable' }).waitFor()
failPreview = false
await page.getByRole('button', { name: 'Preview edition', exact: true }).click()
await page.locator('iframe').waitFor()
await page.getByRole('button', { name: 'Send newsletter test to me', exact: true }).click()
await page.getByRole('status').filter({ hasText: 'Test queued' }).waitFor()
await page.getByLabel('Schedule for (UTC)', { exact: true }).fill('2026-09-18T09:00')
await page.getByRole('button', { name: 'Schedule edition', exact: true }).click()
await page.getByRole('heading', { name: 'Delivery controls', exact: true }).waitFor()
assert.equal(await page.getByLabel('Email subject').isDisabled(), true)
assert.equal(calls.filter((call) => call.path.endsWith('/publish')).at(-1).payload.send_at, '2026-09-18T09:00:00Z')
await page.getByRole('button', { name: 'Cancel edition', exact: true }).click()
await page.getByRole('status').filter({ hasText: 'Edition cancelled' }).waitFor()
await page.getByRole('button', { name: 'Weekly schedule', exact: true }).click()
await page.getByLabel('Send day').selectOption('5')
await page.getByLabel('Enable automatic weekly newsletters').check()
await page.getByRole('button', { name: 'Save weekly settings', exact: true }).click()
await page.getByText('Weekly sending is on', { exact: true }).waitFor()
assert.equal(settings.weekday, 5)
assert.equal(settings.enabled, true)
deliveries = ['sent', 'retry', 'unknown', 'skipped', 'cancelled'].map((state, index) => ({ ...deliveries[0], id: `fixture-${index}`, state, attempts: 1, detail: 'Fixture delivery result.', next_attempt_at: Date.now()/1000+300 }))
await page.getByRole('button', { name: 'Delivery history', exact: true }).click()
await page.getByRole('button', { name: 'Refresh history', exact: true }).click()
await page.getByText('Needs review', { exact: true }).waitFor()
assert.match(await page.locator('.recap-history').innerText(), /Automatic retries are stopped/)
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'History overflows mobile viewport')
for (const width of [1440, 320]) {
await page.setViewportSize({ width, height: 1000 })
tokenState = 'ready'
const before = calls.filter((call) => call.path === '/api/newsletter-subscription/confirm').length
await page.goto(`${base}/newsletter-subscription#action=confirm&token=${'a'.repeat(43)}`)
await page.getByRole('button', { name: 'Confirm newsletter subscription', exact: true }).waitFor()
assert.equal(calls.filter((call) => call.path === '/api/newsletter-subscription/confirm').length, before)
assert.equal(await page.locator('.sidebar').count(), 0)
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Public page overflow at ${width}`)
if (output) await page.screenshot({ path: path.join(output, `newsletter-confirm-${width}.png`), fullPage: true })
await page.getByRole('button', { name: 'Confirm newsletter subscription', exact: true }).click()
await page.getByRole('heading', { name: 'Youre on the list.', exact: true }).waitFor()
assert.equal(new URL(page.url()).hash, '')
tokenState = 'ready'
await page.goto(`${base}/newsletter-subscription#action=unsubscribe&token=${'b'.repeat(43)}`)
await page.getByRole('button', { name: 'Unsubscribe from newsletters', exact: true }).click()
await page.getByRole('heading', { name: 'Newsletters are turned off.', exact: true }).waitFor()
}
tokenState = 'ready'
let started, release
const waiting = new Promise((resolve) => { started = resolve })
heldConfirmation = { started, released: new Promise((resolve) => { release = resolve }) }
await page.goto(`${base}/newsletter-subscription#action=confirm&token=${'c'.repeat(43)}`)
await page.getByRole('button', { name: 'Confirm newsletter subscription', exact: true }).click()
await waiting
await page.goto(`${base}/newsletter-subscription#action=unsubscribe&token=${'d'.repeat(43)}`)
await page.getByRole('button', { name: 'Unsubscribe from newsletters', exact: true }).waitFor()
const applied = page.waitForResponse((response) => response.url().endsWith('/api/newsletter-subscription/confirm'))
heldConfirmation = null
release()
await applied
await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))))
assert.match(new URL(page.url()).hash, /action=unsubscribe/)
assert.equal(await page.getByRole('button', { name: 'Unsubscribe from newsletters', exact: true }).count(), 1)
await page.goto(`${base}/newsletter-subscription#action=confirm&token=bad`)
await page.getByRole('alert').filter({ hasText: 'This email link is incomplete' }).waitFor()
assert.equal(await page.getByRole('button', { name: 'Confirm newsletter subscription', exact: true }).count(), 0)
role = 'user'
await page.goto(`${base}/admin/newsletters`)
await page.waitForURL(`${base}/`)
role = 'unauthorized'
await page.goto(`${base}/admin/newsletters`)
await page.waitForURL(/\/login\?next=%2Fadmin%2Fnewsletters/)
assert.deepEqual(errors, [])
console.log('Newsletter editor, posters, email layouts, consent, stale edits, scheduling, cancellation, history and access redirects passed at 1440/980/390/320px; all API calls used fixtures.')
await context.close()
} finally { await browser.close() }
})().catch((error) => { console.error(error); process.exitCode = 1 })