import { Given, When, Then } from '@cucumber/cucumber' import { expect } from '@playwright/test' import type { AppWorld } from '../support/world.js' import { config } from '../support/types.js' interface ContentWorld extends AppWorld { cardId: string uploadUrl: string signedUrl: string uploadStatus: number } Given('a card exists', async function (this: ContentWorld) { const res = await fetch(`${config.apiUrl}/cards`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ title: 'Attachment test card' }), }) expect(res.ok, `POST /cards failed: ${res.status}`).toBe(true) const body = (await res.json()) as { cardId: string } this.cardId = body.cardId }) When( 'I request an upload URL for filename {string} and content type {string}', async function (this: ContentWorld, fileName: string, contentType: string) { const res = await fetch( `${config.apiUrl}/cards/${this.cardId}/attachments/upload-url`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ cardId: this.cardId, fileName, contentType }), }, ) expect(res.ok, `upload-url request failed: ${res.status} ${await res.text()}`).toBe(true) const body = (await res.json()) as { uploadUrl: string } this.uploadUrl = body.uploadUrl }, ) When( 'I PUT {string} to the upload URL', async function (this: ContentWorld, content: string) { const res = await fetch(this.uploadUrl, { method: 'PUT', body: content, headers: { 'content-type': 'text/plain' }, }) this.uploadStatus = res.status }, ) Then( 'the upload response status is 200 or 201', function (this: ContentWorld) { expect( this.uploadStatus === 200 || this.uploadStatus === 201, `expected 200 or 201 from upload PUT, got ${this.uploadStatus}`, ).toBe(true) }, ) When( 'I request a signed download URL for filename {string}', async function (this: ContentWorld, fileName: string) { const res = await fetch( `${config.apiUrl}/cards/${this.cardId}/attachments/${encodeURIComponent(fileName)}/signed-url`, ) expect(res.ok, `signed-url request failed: ${res.status} ${await res.text()}`).toBe(true) const body = (await res.json()) as { signedUrl: string } this.signedUrl = body.signedUrl }, ) Then( 'the signed URL response contains a signedUrl', function (this: ContentWorld) { expect(typeof this.signedUrl).toBe('string') expect(this.signedUrl.length).toBeGreaterThan(0) }, ) Then( 'downloading the signed URL returns {string}', async function (this: ContentWorld, expectedContent: string) { const res = await fetch(this.signedUrl) expect(res.ok, `signed download failed: ${res.status}`).toBe(true) const text = await res.text() expect(text).toBe(expectedContent) }, ) When('I strip the signature from the signed URL', function (this: ContentWorld) { const url = new URL(this.signedUrl) url.searchParams.delete('sig') this.signedUrl = url.toString() }) Then( 'fetching the unsigned URL returns a 4xx status', async function (this: ContentWorld) { const res = await fetch(this.signedUrl) expect(res.status, `expected 4xx for unsigned access, got ${res.status}`).toBeGreaterThanOrEqual(400) expect(res.status).toBeLessThan(500) }, )