Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ SEEDANCE2_API_KEY=""
# Competitor-remix cost guardrails (optional — sane defaults if unset)
REMIX_BURST_LIMIT="20" # max remix renders per org per 10-min window
REMIX_DAILY_CAP="50" # max remix renders per org per day
# Comma-separated tier allowlist for /api/creatives/remix-jobs. Defaults to
# "t0_5" (borrow-structure-not-pixels only) if unset. t1/t2 use a Tier-2
# hand-picked competitor video as a reference/source and are opt-in only —
# e.g. REMIX_ENABLED_TIERS="t0_5,t1,t2"
REMIX_ENABLED_TIERS=""

# Optional: explicit access token for GCS uploads in local dev
GOOGLE_ACCESS_TOKEN=""
Expand Down Expand Up @@ -59,6 +64,15 @@ CRON_SECRET=""
INGEST_WEBHOOK_SECRET=""
INGEST_ADJUST_SECRET=""

# Remix worker engine (Cloud Run Job, separate repo) — HMAC signing key shared
# with the /api/worker/remix-jobs/* claim/report/upload endpoints. No fallback
# default: unset means every worker request is rejected.
WORKER_WEBHOOK_SECRET=""
# Minutes a claimed-but-stalled RemixJob (claimed/running/assembling/qc with no
# progress) sits before /api/worker/remix-jobs/claim treats it as abandoned and
# lets another worker reclaim it.
REMIX_JOB_LEASE_MINUTES="30"

# Demo seed config (used by `npm run db:seed`).
# - SEED_EMAIL: defaults to demo@adexads.com if unset.
# - SEED_PASSWORD: if unset, the seed script generates a cryptographically
Expand Down
31 changes: 28 additions & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20

services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: adex
POSTGRES_PASSWORD: adex
POSTGRES_DB: adex_e2e
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U adex"
--health-interval 10s
--health-timeout 5s
--health-retries 5

env:
AUTH_TOKEN_SECRET: e2e-test-secret-not-used-for-runtime
DATABASE_URL: postgresql://adex:adex@localhost:5432/adex_e2e
# Testing-only values, not secrets — enable the DB-backed e2e specs
# (competitor-ingest, remix-jobs) that self-skip without them, and let
# e2e/register run without minting real invite codes.
INGEST_WEBHOOK_SECRET: e2e-competitor-ingest-secret
WORKER_WEBHOOK_SECRET: e2e-worker-secret
INVITE_CODES_DISABLED: 'true'

steps:
- uses: actions/checkout@v4

Expand All @@ -26,17 +51,17 @@ jobs:
- name: Generate Prisma client
run: npx prisma generate

- name: Apply migrations
run: npx prisma migrate deploy

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Build app
env:
AUTH_TOKEN_SECRET: e2e-test-secret-not-used-for-runtime
run: npm run build

- name: Run Playwright tests
env:
AUTH_TOKEN_SECRET: e2e-test-secret-not-used-for-runtime
PORT: '3000'
run: npm run test:e2e

Expand Down
8 changes: 1 addition & 7 deletions e2e/competitor-ingest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* Ref: docs/growth/06-competitor-intel-remix.md §4–5 · .claude/rules/testing.md
*/
import { test, expect } from '@playwright/test'
import crypto from 'node:crypto'
import { sign } from './helpers/hmac'

const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || ''
const p = (path: string) => `${BASE_PATH}${path.startsWith('/') ? path : '/' + path}`
Expand Down Expand Up @@ -73,12 +73,6 @@ const SAMPLE_BATCH = {
],
}

function sign(secret: string, body: string) {
const timestamp = String(Math.floor(Date.now() / 1000))
const signature = `sha256=${crypto.createHmac('sha256', secret).update(`${timestamp}:${body}`).digest('hex')}`
return { timestamp, signature }
}

test.describe('competitor ingest — auth gate', () => {
test('missing ?org → 400', async ({ request }) => {
const res = await request.post(p('/api/ingest/competitor'), { data: SAMPLE_BATCH })
Expand Down
15 changes: 15 additions & 0 deletions e2e/helpers/hmac.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Shared HMAC signing helper for e2e specs that hit the HMAC-gated worker/ingest
* routes (src/lib/growth/ingest-auth.ts's contract):
* x-adex-timestamp — epoch seconds
* x-adex-signature — "sha256=" + HMAC_SHA256(secret, `${timestamp}:${payload}`)
*
* Ref: e2e/remix-jobs.spec.ts · e2e/competitor-ingest.spec.ts
*/
import crypto from 'node:crypto'

export function sign(secret: string, payload: string): { timestamp: string; signature: string } {
const timestamp = String(Math.floor(Date.now() / 1000))
const signature = `sha256=${crypto.createHmac('sha256', secret).update(`${timestamp}:${payload}`).digest('hex')}`
return { timestamp, signature }
}
Loading
Loading