mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 10:19:43 +00:00
chore(harness): make repository agent-ready
Add concise routed context, task-aware agent roles, focused skills, and structured learning promotion.\n\nUnify local development and workspace tooling, add layered test and E2E verification, harden MCP authentication, and gate deployment behind verification and explicit production promotion.
This commit is contained in:
1 parent
5a69bbc1a5
commit
01bb8225ae
125 files changed
+5078
-24529
No files matched your search
@@ -0,0 +1,9 @@
|
||||
# System E2E
|
||||
|
||||
Tests assume `scripts/dev-local.sh up` is already running. Execute with
|
||||
`npm run verify:e2e`; the wrapper supplies local-only Supabase credentials and
|
||||
refuses remote hosts. Playwright retains traces, screenshots, and videos on
|
||||
failure under ignored output directories.
|
||||
|
||||
Native flows live under `maestro/` and run with `npm run test:mobile:e2e` after an
|
||||
iOS or Android build is installed.
|
||||
@@ -0,0 +1,36 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
export function localSupabase() {
|
||||
const url = process.env.SUPABASE_URL;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
if (!url || !serviceKey) throw new Error('Run E2E through npm run verify:e2e');
|
||||
const host = new URL(url).hostname;
|
||||
if (host !== '127.0.0.1' && host !== 'localhost') {
|
||||
throw new Error(`E2E refuses non-local Supabase host: ${host}`);
|
||||
}
|
||||
return createClient(url, serviceKey, {
|
||||
auth: { autoRefreshToken: false, persistSession: false },
|
||||
});
|
||||
}
|
||||
|
||||
export async function createRedemptionFixture() {
|
||||
const client = localSupabase();
|
||||
const nonce = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const email = `redeem-${nonce}@example.test`;
|
||||
const code = `E2E-${nonce}`.toUpperCase();
|
||||
const { data: created, error: userError } = await client.auth.admin.createUser({
|
||||
email,
|
||||
password: 'Sauci-e2e-only-2026!',
|
||||
email_confirm: true,
|
||||
});
|
||||
if (userError || !created.user) throw userError ?? new Error('User creation failed');
|
||||
const { data: redemptionCode, error: codeError } = await client.from('redemption_codes').insert({
|
||||
code,
|
||||
description: 'Playwright local fixture',
|
||||
max_uses: 1,
|
||||
is_active: true,
|
||||
expires_at: new Date(Date.now() + 3_600_000).toISOString(),
|
||||
}).select().single();
|
||||
if (codeError) throw codeError;
|
||||
return { client, email, code, userId: created.user.id, codeId: redemptionCode.id };
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
appId: com.sauci.app
|
||||
name: Local fixture login reaches authenticated app
|
||||
---
|
||||
- launchApp:
|
||||
clearState: true
|
||||
- tapOn: "Password"
|
||||
- tapOn: "Enter your email"
|
||||
- inputText: "e2e.alex@example.test"
|
||||
- tapOn: "Enter your password"
|
||||
- inputText: "Sauci-e2e-only-2026!"
|
||||
- tapOn: "Sign In"
|
||||
- extendedWaitUntil:
|
||||
notVisible: "Sign In"
|
||||
timeout: 15000
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@sauci/e2e",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "playwright test",
|
||||
"test:headed": "playwright test --headed",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "tsc --noEmit",
|
||||
"build": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@supabase/supabase-js": "^2.49.1",
|
||||
"@types/node": "^20.17.19",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './specs',
|
||||
timeout: 30_000,
|
||||
expect: { timeout: 8_000 },
|
||||
forbidOnly: Boolean(process.env.CI),
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: process.env.CI ? [['html', { open: 'never' }], ['line']] : 'list',
|
||||
outputDir: 'test-results',
|
||||
use: {
|
||||
trace: 'retain-on-failure',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{ name: 'chromium', use: { browserName: 'chromium' } },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const admin = process.env.ADMIN_URL ?? 'http://127.0.0.1:3001';
|
||||
|
||||
test('unauthenticated admin access reaches the real login boundary', async ({ page }) => {
|
||||
await page.goto(admin);
|
||||
await expect(page).toHaveURL(/\/login$/);
|
||||
await expect(page.getByRole('heading', { name: 'Welcome Back' })).toBeVisible();
|
||||
await expect(page.getByLabel('Email Address')).toBeVisible();
|
||||
await expect(page.getByLabel('Password')).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const mcp = process.env.MCP_URL ?? 'http://127.0.0.1:3002';
|
||||
|
||||
test('MCP health is available while tool access requires auth', async ({ request }) => {
|
||||
const health = await request.get(`${mcp}/health`);
|
||||
expect(health.ok()).toBe(true);
|
||||
expect(await health.json()).toMatchObject({ status: 'ok', service: 'sauci-mcp' });
|
||||
const tools = await request.post(`${mcp}/mcp`);
|
||||
expect(tools.status()).toBe(401);
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { createRedemptionFixture } from '../helpers/local-supabase';
|
||||
|
||||
const web = process.env.WEB_URL ?? 'http://127.0.0.1:3000';
|
||||
|
||||
test('marketing home renders the primary product journey', async ({ page }) => {
|
||||
await page.goto(web);
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: /get started|download/i }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('redemption validates input without calling the backend', async ({ page }) => {
|
||||
await page.goto(`${web}/redeem`);
|
||||
await page.getByLabel('Email Address').fill('not-an-email');
|
||||
await page.getByLabel('Redemption Code').fill('ANY-CODE');
|
||||
await page.getByRole('button', { name: 'Redeem Code' }).click();
|
||||
await expect(page.getByText('Please enter a valid email address')).toBeVisible();
|
||||
});
|
||||
|
||||
test('redemption updates server state and visible product state', async ({ page }) => {
|
||||
const fixture = await createRedemptionFixture();
|
||||
try {
|
||||
await page.goto(`${web}/redeem`);
|
||||
await page.getByLabel('Email Address').fill(fixture.email);
|
||||
await page.getByLabel('Redemption Code').fill(fixture.code);
|
||||
await page.getByRole('button', { name: 'Redeem Code' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Code Redeemed!' })).toBeVisible();
|
||||
await expect(page.getByText(fixture.email)).toBeVisible();
|
||||
|
||||
const { data: profile, error: profileError } = await fixture.client
|
||||
.from('profiles').select('is_premium').eq('id', fixture.userId).single();
|
||||
expect(profileError).toBeNull();
|
||||
expect(profile?.is_premium).toBe(true);
|
||||
const { count, error: redemptionError } = await fixture.client
|
||||
.from('code_redemptions').select('*', { count: 'exact', head: true })
|
||||
.eq('code_id', fixture.codeId).eq('user_id', fixture.userId);
|
||||
expect(redemptionError).toBeNull();
|
||||
expect(count).toBe(1);
|
||||
} finally {
|
||||
await fixture.client.auth.admin.deleteUser(fixture.userId);
|
||||
await fixture.client.from('redemption_codes').delete().eq('id', fixture.codeId);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"types": ["node", "@playwright/test"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
Reference in new issue
Block a user