nextjs-hackathon-stack 0.1.40 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +3 -63
- package/package.json +1 -1
- package/template/.claude/agents/backend.md +54 -0
- package/template/.claude/agents/business-analyst.md +195 -0
- package/template/.claude/agents/code-reviewer.md +76 -0
- package/template/.claude/agents/frontend.md +85 -0
- package/template/.claude/agents/security-researcher.md +54 -0
- package/template/.claude/agents/technical-lead.md +92 -0
- package/template/.claude/agents/test-qa.md +85 -0
- package/template/.claude/rules/architecture.mdc +48 -0
- package/template/.claude/rules/coding-standards.mdc +120 -0
- package/template/.claude/rules/components.mdc +49 -0
- package/template/.claude/rules/data-fetching.mdc +115 -0
- package/template/.claude/rules/forms.mdc +100 -0
- package/template/.claude/rules/general.mdc +54 -0
- package/template/.claude/rules/migrations.mdc +11 -0
- package/template/.claude/rules/nextjs.mdc +71 -0
- package/template/.claude/rules/security.mdc +108 -0
- package/template/.claude/rules/supabase.mdc +70 -0
- package/template/.claude/rules/testing.mdc +136 -0
- package/template/.claude/settings.json +16 -0
- package/template/.claude/skills/build-feature/SKILL.md +198 -0
- package/template/.claude/skills/build-feature/references/server-action-test-template.md +103 -0
- package/template/.claude/skills/create-api-route/SKILL.md +62 -0
- package/template/.claude/skills/discover-feature/SKILL.md +200 -0
- package/template/.claude/skills/memory/SKILL.md +208 -0
- package/template/.claude/skills/review-branch/SKILL.md +43 -0
- package/template/.claude/skills/review-branch/references/review-checklist.md +36 -0
- package/template/.claude/skills/security-audit/SKILL.md +40 -0
- package/template/.claude/skills/security-audit/references/audit-steps.md +41 -0
- package/template/.claude/skills/supabase/SKILL.md +105 -0
- package/template/.claude/skills/supabase/assets/feedback-issue-template.md +17 -0
- package/template/.claude/skills/supabase/references/skill-feedback.md +17 -0
- package/template/.claude/skills/supabase-postgres-best-practices/SKILL.md +65 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp__contributing.md +170 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp__sections.md +39 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp__template.md +34 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_advanced-full-text-search.md +55 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_advanced-jsonb-indexing.md +49 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_conn-idle-timeout.md +46 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_conn-limits.md +44 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_conn-pooling.md +41 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_conn-prepared-statements.md +46 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_data-batch-inserts.md +54 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_data-n-plus-one.md +53 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_data-pagination.md +50 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_data-upsert.md +50 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_lock-advisory.md +56 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_lock-deadlock-prevention.md +68 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_lock-short-transactions.md +50 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_lock-skip-locked.md +54 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_monitor-explain-analyze.md +45 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_monitor-pg-stat-statements.md +55 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_monitor-vacuum-analyze.md +55 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_query-composite-indexes.md +44 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_query-covering-indexes.md +40 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_query-index-types.md +48 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_query-missing-indexes.md +43 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_query-partial-indexes.md +45 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_schema-constraints.md +80 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_schema-data-types.md +46 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_schema-foreign-key-indexes.md +59 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_schema-lowercase-identifiers.md +55 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_schema-partitioning.md +55 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_schema-primary-keys.md +61 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_security-privileges.md +54 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_security-rls-basics.md +50 -0
- package/template/.claude/skills/supabase-postgres-best-practices/references/pgbp_security-rls-performance.md +57 -0
- package/template/.cursor/agents/business-analyst.md +197 -0
- package/template/.cursor/agents/technical-lead.md +3 -3
- package/template/.cursor/mcp.json +6 -2
- package/template/.cursor/skills/build-feature/SKILL.md +20 -21
- package/template/.cursor/skills/discover-feature/SKILL.md +118 -29
- package/template/.cursor/skills/supabase/SKILL.md +104 -0
- package/template/.cursor/skills/supabase/assets/feedback-issue-template.md +17 -0
- package/template/.cursor/skills/supabase/references/skill-feedback.md +17 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp__contributing.md +170 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp__sections.md +39 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp__template.md +34 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_advanced-full-text-search.md +55 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_advanced-jsonb-indexing.md +49 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_conn-idle-timeout.md +46 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_conn-limits.md +44 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_conn-pooling.md +41 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_conn-prepared-statements.md +46 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_data-batch-inserts.md +54 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_data-n-plus-one.md +53 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_data-pagination.md +50 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_data-upsert.md +50 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_lock-advisory.md +56 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_lock-deadlock-prevention.md +68 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_lock-short-transactions.md +50 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_lock-skip-locked.md +54 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_monitor-explain-analyze.md +45 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_monitor-pg-stat-statements.md +55 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_monitor-vacuum-analyze.md +55 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_query-composite-indexes.md +44 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_query-covering-indexes.md +40 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_query-index-types.md +48 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_query-missing-indexes.md +43 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_query-partial-indexes.md +45 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_schema-constraints.md +80 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_schema-data-types.md +46 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_schema-foreign-key-indexes.md +59 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_schema-lowercase-identifiers.md +55 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_schema-partitioning.md +55 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_schema-primary-keys.md +61 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_security-privileges.md +54 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_security-rls-basics.md +50 -0
- package/template/.cursor/skills/supabase-postgres-best-practices/references/pgbp_security-rls-performance.md +57 -0
- package/template/.mcp.json +16 -0
- package/template/.opencode/agents/backend.md +72 -0
- package/template/.opencode/agents/business-analyst.md +153 -0
- package/template/.opencode/agents/code-reviewer.md +80 -0
- package/template/.opencode/agents/frontend.md +84 -0
- package/template/.opencode/agents/security-researcher.md +58 -0
- package/template/.opencode/agents/technical-lead.md +131 -0
- package/template/.opencode/agents/test-qa.md +103 -0
- package/template/.opencode/memory/architecture-snapshot.md +127 -0
- package/template/.opencode/skills/build-feature/SKILL.md +208 -0
- package/template/.opencode/skills/create-api-route/SKILL.md +63 -0
- package/template/.opencode/skills/discover-feature/SKILL.md +194 -0
- package/template/.opencode/skills/memory/SKILL.md +199 -0
- package/template/.opencode/skills/review-branch/SKILL.md +43 -0
- package/template/.opencode/skills/security-audit/SKILL.md +40 -0
- package/template/.opencode/skills/supabase/SKILL.md +105 -0
- package/template/.opencode/skills/supabase/assets/feedback-issue-template.md +17 -0
- package/template/.opencode/skills/supabase/references/skill-feedback.md +17 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/SKILL.md +65 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp__contributing.md +170 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp__sections.md +39 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp__template.md +34 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_advanced-full-text-search.md +55 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_advanced-jsonb-indexing.md +49 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_conn-idle-timeout.md +46 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_conn-limits.md +44 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_conn-pooling.md +41 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_conn-prepared-statements.md +46 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_data-batch-inserts.md +54 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_data-n-plus-one.md +53 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_data-pagination.md +50 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_data-upsert.md +50 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_lock-advisory.md +56 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_lock-deadlock-prevention.md +68 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_lock-short-transactions.md +50 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_lock-skip-locked.md +54 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_monitor-explain-analyze.md +45 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_monitor-pg-stat-statements.md +55 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_monitor-vacuum-analyze.md +55 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_query-composite-indexes.md +44 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_query-covering-indexes.md +40 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_query-index-types.md +48 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_query-missing-indexes.md +43 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_query-partial-indexes.md +45 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_schema-constraints.md +80 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_schema-data-types.md +46 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_schema-foreign-key-indexes.md +59 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_schema-lowercase-identifiers.md +55 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_schema-partitioning.md +55 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_schema-primary-keys.md +61 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_security-privileges.md +54 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_security-rls-basics.md +50 -0
- package/template/.opencode/skills/supabase-postgres-best-practices/references/pgbp_security-rls-performance.md +57 -0
- package/template/.requirements/README.md +1 -1
- package/template/AGENTS.md +1 -1
- package/template/CLAUDE.md +1 -1
- package/template/README.md +15 -2
- package/template/_gitignore +3 -0
- package/template/docker-compose.yml +26 -0
- package/template/ia-flow.md +341 -0
- package/template/opencode.json +23 -0
- package/template/.cursor/agents/business-intelligence.md +0 -83
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Security rules (OWASP, auth, RLS, XSS, CSP). Applied to server code, API routes, actions, auth, and config.
|
|
3
|
+
globs: ["src/**/actions/**", "src/**/api/**", "src/**/lib/supabase/**", "src/app/**/route.ts", "src/app/**/proxy.ts", "*.config.*"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Security Rules
|
|
8
|
+
|
|
9
|
+
## OWASP Top 10 Awareness
|
|
10
|
+
1. **Injection** — use Drizzle parameterized queries, never string concatenation
|
|
11
|
+
2. **Broken Auth** — Supabase Auth only, no custom auth
|
|
12
|
+
3. **Sensitive Data** — HTTPS-only cookies, no secrets in code
|
|
13
|
+
4. **XSS** — never use `dangerouslySetInnerHTML`, escape user input
|
|
14
|
+
5. **Broken Access Control** — RLS on all tables, auth check in all protected routes
|
|
15
|
+
6. **Security Misconfiguration** — CSP, HSTS, X-Frame-Options in `next.config.ts`
|
|
16
|
+
7. **Insecure Dependencies** — run `pnpm audit` regularly
|
|
17
|
+
8. **SSRF** — validate URLs before fetch
|
|
18
|
+
|
|
19
|
+
## Environment Variables
|
|
20
|
+
```
|
|
21
|
+
NEXT_PUBLIC_* → client-safe (Supabase URL, anon key only)
|
|
22
|
+
No prefix → server-only (DB URL, secret keys)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Never use `NEXT_PUBLIC_` for secret keys.
|
|
26
|
+
|
|
27
|
+
## Supabase RLS
|
|
28
|
+
- Enable RLS on EVERY table
|
|
29
|
+
- Write explicit allow/deny policies
|
|
30
|
+
- Test RLS policies — never assume they work
|
|
31
|
+
|
|
32
|
+
## Auth in API Routes and Server Actions
|
|
33
|
+
|
|
34
|
+
Every Server Action and API route that accesses data must verify auth via `getUser()` before any data access. RLS provides row-level enforcement, but `getUser()` at the action level is required as defense-in-depth — never rely on RLS alone.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
export async function POST(request: Request) {
|
|
38
|
+
const supabase = await createClient();
|
|
39
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
40
|
+
if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
41
|
+
// ...
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Every mutation Server Action must:
|
|
46
|
+
1. Call `getUser()` and return an error result if no user
|
|
47
|
+
2. Scope all queries to `user.id` (even with RLS active)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
export async function deleteItemAction(id: string): Promise<ActionResult> {
|
|
51
|
+
const supabase = await createClient();
|
|
52
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
53
|
+
if (!user) return { status: "error", message: "No autenticado" };
|
|
54
|
+
|
|
55
|
+
const { error } = await supabase.from("items").delete().eq("id", id).eq("user_id", user.id);
|
|
56
|
+
if (error) return { status: "error", message: "Error al eliminar" };
|
|
57
|
+
|
|
58
|
+
revalidatePath("/");
|
|
59
|
+
return { status: "success", message: "Eliminado" };
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Open Redirect Prevention
|
|
64
|
+
|
|
65
|
+
Validate all redirect targets — ensure `next`/`redirect` params start with `/` and not `//`. Never redirect to user-controlled URLs without validation.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// ✅ Safe redirect validation
|
|
69
|
+
const next = rawNext.startsWith("/") && !rawNext.startsWith("//") ? rawNext : "/";
|
|
70
|
+
|
|
71
|
+
// ❌ Unsafe — allows protocol-relative redirects (//evil.com)
|
|
72
|
+
const next = searchParams.get("next") ?? "/";
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Input Validation
|
|
76
|
+
- Validate ALL external input with Zod at the boundary
|
|
77
|
+
- Validate in Server Actions AND API routes
|
|
78
|
+
- Never trust client-side validation alone
|
|
79
|
+
|
|
80
|
+
## Rate Limiting
|
|
81
|
+
- Apply rate limiting to all AI routes
|
|
82
|
+
- Apply rate limiting to auth endpoints
|
|
83
|
+
- Use Vercel's built-in rate limiting or `@upstash/ratelimit`
|
|
84
|
+
|
|
85
|
+
## Content Security Policy
|
|
86
|
+
|
|
87
|
+
`script-src` must not include `'unsafe-eval'`. Avoid `'unsafe-inline'` for scripts — prefer nonce-based CSP. `'unsafe-inline'` is acceptable for `style-src` only.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// ✅ Ideal — nonce-based CSP (no unsafe-inline for scripts)
|
|
91
|
+
`script-src 'self' 'nonce-${nonce}'`,
|
|
92
|
+
"style-src 'self' 'unsafe-inline'",
|
|
93
|
+
|
|
94
|
+
// ✅ Acceptable intermediate — add a TODO to migrate to nonce-based
|
|
95
|
+
"script-src 'self' 'unsafe-inline'", // TODO: replace with nonce-based CSP
|
|
96
|
+
"style-src 'self' 'unsafe-inline'",
|
|
97
|
+
|
|
98
|
+
// ✅ Dev-only — unsafe-eval for React debugging features (silences installHook.js warning)
|
|
99
|
+
`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""}`,
|
|
100
|
+
|
|
101
|
+
// ❌ Never — unsafe-eval unconditionally in production
|
|
102
|
+
"script-src 'self' 'unsafe-eval' 'unsafe-inline'",
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Cookies
|
|
106
|
+
- `httpOnly: true` for auth tokens (Supabase handles this)
|
|
107
|
+
- `secure: true` in production
|
|
108
|
+
- `sameSite: "lax"` minimum
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Supabase + Drizzle rules. Schema changes require migration — never edit migration files directly.
|
|
3
|
+
globs: ["src/shared/db/**", "src/shared/lib/supabase/**"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Supabase + Drizzle Rules (Risk 2: Zod Schema Drift)
|
|
8
|
+
|
|
9
|
+
## Drizzle: Schema + Migrations ONLY
|
|
10
|
+
```typescript
|
|
11
|
+
// ✅ Use Drizzle for schema definition
|
|
12
|
+
export const users = pgTable("users", {
|
|
13
|
+
id: uuid("id").primaryKey(),
|
|
14
|
+
email: text("email").notNull(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// ✅ Auto-generate Zod from Drizzle — zero manual Zod for DB types
|
|
18
|
+
export const insertUserSchema = createInsertSchema(users);
|
|
19
|
+
export const selectUserSchema = createSelectSchema(users);
|
|
20
|
+
|
|
21
|
+
// ❌ NEVER write Zod schemas manually for DB types
|
|
22
|
+
const userSchema = z.object({ id: z.string(), email: z.string() }); // WRONG
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Runtime Reads: Repository Pattern (RLS active)
|
|
26
|
+
|
|
27
|
+
All reads go through the feature's `queries/` module — never call `supabase.from().select()` inline in pages or actions.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// ✅ Read via repository function
|
|
31
|
+
import { getUserById } from "@/features/users/queries/users.queries";
|
|
32
|
+
const { data, error } = await getUserById(supabase, userId);
|
|
33
|
+
|
|
34
|
+
// ✅ Repository module — receives supabase client as parameter (DI)
|
|
35
|
+
// src/features/users/queries/users.queries.ts
|
|
36
|
+
import type { createClient } from "@/shared/lib/supabase/server";
|
|
37
|
+
type Client = Awaited<ReturnType<typeof createClient>>;
|
|
38
|
+
|
|
39
|
+
export async function getUserById(supabase: Client, userId: string) {
|
|
40
|
+
return supabase.from("users").select("*").eq("id", userId).single();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ❌ Never inline a SELECT in a page or action
|
|
44
|
+
const { data } = await supabase.from("users").select("*").eq("id", userId); // WRONG in page/action
|
|
45
|
+
|
|
46
|
+
// ❌ Never use Drizzle for runtime queries
|
|
47
|
+
const users = await db.select().from(usersTable); // WRONG — bypasses RLS
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## RLS Mandate
|
|
51
|
+
- Enable RLS on ALL tables in Supabase dashboard
|
|
52
|
+
- Every table must have explicit policies — RLS is not a post-step
|
|
53
|
+
- Default policy template: `CREATE POLICY "users_own_rows" ON <table> FOR ALL USING (user_id = auth.uid());`
|
|
54
|
+
|
|
55
|
+
## Migrations
|
|
56
|
+
|
|
57
|
+
Files in `src/shared/db/migrations/` are **auto-generated** by Drizzle CLI — never create, edit, or delete them directly.
|
|
58
|
+
|
|
59
|
+
| Command | Purpose |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `pnpm db:generate` | Generate migration SQL from schema changes |
|
|
62
|
+
| `pnpm db:migrate` | Apply pending migrations to the database |
|
|
63
|
+
| `pnpm db:push` | Push schema directly to DB (dev only, no migration file) |
|
|
64
|
+
|
|
65
|
+
Workflow: edit `src/shared/db/<table>.schema.ts` → `pnpm db:generate` → review SQL → `pnpm db:migrate`
|
|
66
|
+
|
|
67
|
+
## Environment Variables
|
|
68
|
+
- `NEXT_PUBLIC_SUPABASE_URL` + `NEXT_PUBLIC_SUPABASE_ANON_KEY` — client-safe, used in browser + server
|
|
69
|
+
- `DATABASE_URL` — server-only, used by Drizzle for migrations (`drizzle.config.ts`)
|
|
70
|
+
- See `.env.example` for all required variables
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: TDD, Vitest, and Playwright testing standards.
|
|
3
|
+
globs: ["**/*.test.*", "**/e2e/**"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing Standards
|
|
8
|
+
|
|
9
|
+
## Iron Rules (Non-Negotiable)
|
|
10
|
+
1. **NEVER modify a test to make it pass** — always fix the implementation code. Tests define the contract. If a test fails, the code is wrong, not the test.
|
|
11
|
+
2. **Tests are immutable during GREEN phase** — only modify tests in RED phase (writing new ones) or REFACTOR phase (improving structure, never weakening assertions)
|
|
12
|
+
3. **Coverage thresholds** — 95% statements/functions/lines, 90% branches. Use `/* v8 ignore next */` only for genuinely unreachable defensive branches
|
|
13
|
+
4. **Every edge case gets a test** — null/undefined, empty string, empty array, boundary values, error responses, timeout, concurrent calls
|
|
14
|
+
5. **AAA is mandatory** — every test uses Arrange-Act-Assert with labeled comments
|
|
15
|
+
|
|
16
|
+
## TDD Cycle
|
|
17
|
+
1. **RED**: Write failing test describing desired behavior
|
|
18
|
+
2. **GREEN**: Write minimum code to make it pass
|
|
19
|
+
3. **REFACTOR**: Clean up while keeping tests green
|
|
20
|
+
|
|
21
|
+
## AAA Pattern (Arrange-Act-Assert)
|
|
22
|
+
|
|
23
|
+
Every test MUST follow the Arrange-Act-Assert structure. No exceptions.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
it("shows error message on invalid credentials", async () => {
|
|
27
|
+
// Arrange
|
|
28
|
+
const user = userEvent.setup();
|
|
29
|
+
mockSignIn.mockResolvedValue({ error: { message: "Invalid credentials" } });
|
|
30
|
+
render(<LoginForm />);
|
|
31
|
+
|
|
32
|
+
// Act
|
|
33
|
+
await user.type(screen.getByLabelText(/email/i), "test@example.com");
|
|
34
|
+
await user.type(screen.getByLabelText(/password/i), "password123");
|
|
35
|
+
await user.click(screen.getByRole("button", { name: /sign in/i }));
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
expect(await screen.findByRole("alert")).toHaveTextContent("Invalid credentials");
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Rules:
|
|
43
|
+
- **Arrange**: set up state, mocks, render
|
|
44
|
+
- **Act**: perform the single action being tested
|
|
45
|
+
- **Assert**: verify outcome — one logical assertion per test
|
|
46
|
+
- Add blank lines between sections when all three are non-trivial
|
|
47
|
+
- Never skip a section — if Act is empty, you are testing the wrong thing
|
|
48
|
+
|
|
49
|
+
## Vitest (Unit + Integration)
|
|
50
|
+
- Mock external dependencies only (HTTP calls, Supabase, DB)
|
|
51
|
+
- Test behavior, not implementation details
|
|
52
|
+
- Colocate tests with source: `features/auth/__tests__/`
|
|
53
|
+
- Coverage thresholds are non-negotiable (95% statements/functions/lines, 90% branches — see Iron Rule #3)
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// ✅ AAA — tests behavior
|
|
57
|
+
it("shows error message on invalid credentials", async () => {
|
|
58
|
+
// Arrange
|
|
59
|
+
mockSignIn.mockResolvedValue({ error: { message: "Invalid credentials" } });
|
|
60
|
+
render(<LoginForm />);
|
|
61
|
+
|
|
62
|
+
// Act
|
|
63
|
+
await userEvent.click(screen.getByRole("button", { name: /sign in/i }));
|
|
64
|
+
|
|
65
|
+
// Assert
|
|
66
|
+
expect(await screen.findByRole("alert")).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ❌ No AAA — tests implementation details, brittle
|
|
70
|
+
it("calls signInWithPassword with correct args", () => { ... });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Playwright (E2E)
|
|
74
|
+
- **Always use `data-testid` attributes for element selection** — never use visible text or translated labels. Text-based selectors break when copy or i18n changes.
|
|
75
|
+
- Page Object Pattern for complex flows
|
|
76
|
+
- Every user flow needs an e2e test
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// ✅ Stable — decoupled from i18n
|
|
80
|
+
await page.getByTestId("submit-button").click();
|
|
81
|
+
|
|
82
|
+
// ❌ Brittle — breaks if language changes or copy is updated
|
|
83
|
+
await page.getByRole("button", { name: /sign in/i }).click();
|
|
84
|
+
await page.getByLabel(/email/i).fill("...");
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
test("user can log in", async ({ page }) => {
|
|
89
|
+
// Arrange
|
|
90
|
+
await page.goto("/login");
|
|
91
|
+
|
|
92
|
+
// Act
|
|
93
|
+
await page.getByTestId("email-input").fill("user@example.com");
|
|
94
|
+
await page.getByTestId("password-input").fill("password123");
|
|
95
|
+
await page.getByTestId("sign-in-button").click();
|
|
96
|
+
|
|
97
|
+
// Assert
|
|
98
|
+
await expect(page).toHaveURL("/");
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Edge Case Checklist
|
|
103
|
+
For every function, ask: what happens with...
|
|
104
|
+
- null / undefined input
|
|
105
|
+
- empty string / empty array / empty object
|
|
106
|
+
- boundary values (0, -1, MAX_SAFE_INTEGER)
|
|
107
|
+
- invalid types (wrong shape, missing fields)
|
|
108
|
+
- API error responses (4xx, 5xx, network failure)
|
|
109
|
+
- Loading / pending states
|
|
110
|
+
- Concurrent calls / race conditions
|
|
111
|
+
- Auth expired / missing session
|
|
112
|
+
|
|
113
|
+
If any of these apply, write a test for it. No exceptions.
|
|
114
|
+
|
|
115
|
+
## jsdom Known Limitations (avoid rework cycles)
|
|
116
|
+
|
|
117
|
+
jsdom does not implement all browser APIs. Hitting these in tests causes failures that require full component rewrites — avoid them upfront:
|
|
118
|
+
|
|
119
|
+
- **No `HTMLDialogElement` methods** — `dialog.showModal()` and `dialog.close()` throw in jsdom. Use state-controlled visibility (`isOpen` prop / conditional render) instead of the native `<dialog>` API.
|
|
120
|
+
- **No portal-based components in unit tests** — components like `<Toaster>` (sonner), `<Tooltip>`, `<Popover>` render outside the React tree and are unreliable in jsdom. Exclude their wrapper files from coverage; test toast behavior indirectly via the action's `ActionResult`.
|
|
121
|
+
- **Run `pnpm lint` before finishing** — do not leave lint errors to a separate pass. Fix inline as you go.
|
|
122
|
+
|
|
123
|
+
## Coverage Thresholds
|
|
124
|
+
```typescript
|
|
125
|
+
// vitest.config.ts
|
|
126
|
+
coverage: {
|
|
127
|
+
thresholds: {
|
|
128
|
+
branches: 90,
|
|
129
|
+
functions: 95,
|
|
130
|
+
lines: 95,
|
|
131
|
+
statements: 95,
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Use `/* v8 ignore next */` for genuinely unreachable defensive branches (e.g., exhaustive `switch` default arms, type narrowing guards that cannot be hit at runtime).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"memory": {
|
|
4
|
+
"type": "http",
|
|
5
|
+
"url": "http://localhost:8765/mcp"
|
|
6
|
+
},
|
|
7
|
+
"shadcn": {
|
|
8
|
+
"command": "npx",
|
|
9
|
+
"args": ["-y", "shadcn@latest", "mcp"]
|
|
10
|
+
},
|
|
11
|
+
"supabase": {
|
|
12
|
+
"type": "http",
|
|
13
|
+
"url": "https://mcp.supabase.com/mcp?features=docs"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-feature
|
|
3
|
+
description: Plan and implement a new feature following TDD — tech-lead planning, backend + frontend in parallel, review gate, and memory sync. Run in a fresh conversation after /discover-feature. Triggers: 'build feature', 'implement feature', 'start building', 'build from requirements'. NOT for: bug fixes, refactors, or API-only routes (use /create-api-route).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build Feature Skill
|
|
7
|
+
|
|
8
|
+
> **Invoke as:** `/build-feature @.requirements/<feature-name>-<timestamp>.md`
|
|
9
|
+
> Run in **Agent mode** in a **fresh conversation** (not the same one where you ran `/discover-feature`).
|
|
10
|
+
|
|
11
|
+
## IMPORTANT: Fresh Conversation Required
|
|
12
|
+
|
|
13
|
+
If this conversation already contains requirements gathering, **start a new conversation** first. Reusing the same context risks hitting the token limit mid-implementation.
|
|
14
|
+
|
|
15
|
+
If context usage exceeds 60% at any point, stop and tell the user to continue in a new conversation referencing the current step.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Process
|
|
20
|
+
|
|
21
|
+
### 1. Read the Requirements
|
|
22
|
+
|
|
23
|
+
Read `.requirements/<feature-name>-<timestamp>.md`. Confirm the file exists and contains both a **Functional Task List** (with acceptance criteria) and a **Technical Task List** (with Parallel Execution Plan groups A/B/C) before proceeding.
|
|
24
|
+
|
|
25
|
+
- If no spec exists → tell the user to run `/discover-feature <description>` first
|
|
26
|
+
- If the file only has functional tasks (old format) → use the `technical-lead` subagent to produce the Technical Task List before continuing
|
|
27
|
+
|
|
28
|
+
### 2. Load Architecture Context via MCP Memory
|
|
29
|
+
|
|
30
|
+
Load project context with targeted queries (token-efficient):
|
|
31
|
+
|
|
32
|
+
1. Read `package.json` to get the project name (`<project-name>`)
|
|
33
|
+
2. `search_memory` with `tags: ["project:<project-name>", "domain:ui"]` — installed shadcn/ui components
|
|
34
|
+
3. `search_memory` with `tags: ["project:<project-name>", "domain:database"]` — existing schema
|
|
35
|
+
4. `search_memory` with `tags: ["project:<project-name>", "domain:features"]` — existing features and paths
|
|
36
|
+
5. `search_memory` with `tags: ["project:<project-name>", "domain:patterns"]` — canonical pattern references
|
|
37
|
+
6. `search_memory` with `tags: ["project:<project-name>", "domain:rules"]` — active lint/TS rules
|
|
38
|
+
|
|
39
|
+
**Fallback**: if memory service is unavailable or returns no results, read `.cursor/memory/architecture-snapshot.md` directly.
|
|
40
|
+
|
|
41
|
+
**Also read `eslint.config.ts` and `tsconfig.json`** to confirm active rules and compiler flags before writing any code.
|
|
42
|
+
|
|
43
|
+
**Dependency pre-flight:**
|
|
44
|
+
```bash
|
|
45
|
+
pnpm ls drizzle-orm # Confirm installed version
|
|
46
|
+
pnpm typecheck # Confirm baseline passes — fix pre-existing errors first
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Planning (Tech Lead)
|
|
50
|
+
|
|
51
|
+
The requirements file already contains a Technical Task List with parallel execution groups produced during `/discover-feature`. The TL should:
|
|
52
|
+
|
|
53
|
+
1. **Read the Parallel Execution Plan** from the requirements file (Groups A/B/C + Review Gate)
|
|
54
|
+
2. **Verify against current codebase state** — confirm referenced patterns, schemas, and components still exist
|
|
55
|
+
3. **Identify reuse** from MCP memory:
|
|
56
|
+
- Which canonical patterns to copy
|
|
57
|
+
- Which shadcn components are already installed vs need installing
|
|
58
|
+
- Which shared utilities apply (`formFieldText`, `firstZodIssueMessage`, etc.)
|
|
59
|
+
4. **Plan the test structure**:
|
|
60
|
+
- One `describe` block per acceptance criterion
|
|
61
|
+
- Which mocks are needed (`makeSupabaseMock`, HTTP mocks, etc.)
|
|
62
|
+
- Which edge cases map to test cases from the Functional Task List
|
|
63
|
+
5. **Present a brief implementation summary to the user** and wait for approval before proceeding to Step 4.
|
|
64
|
+
|
|
65
|
+
> Summary: execution order (which groups run in parallel), which files will be created/modified, any risks or deviations from the plan.
|
|
66
|
+
|
|
67
|
+
### 4. Create Feature Structure
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
src/features/<feature-name>/
|
|
71
|
+
├── components/
|
|
72
|
+
├── actions/
|
|
73
|
+
├── queries/
|
|
74
|
+
├── hooks/
|
|
75
|
+
├── api/
|
|
76
|
+
├── lib/
|
|
77
|
+
└── __tests__/
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 5. Pre-Test Setup
|
|
81
|
+
|
|
82
|
+
Update `vitest.config.ts` to exclude files that cannot be meaningfully tested in jsdom:
|
|
83
|
+
- Drizzle schema file(s) (`src/shared/db/*.schema.ts`)
|
|
84
|
+
- Pure type-only files
|
|
85
|
+
- Portal/browser-API-dependent UI wrappers (e.g., `src/shared/components/ui/sonner.tsx`)
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// vitest.config.ts — add to coverage.exclude before writing tests
|
|
89
|
+
exclude: [
|
|
90
|
+
"src/shared/db/*.schema.ts",
|
|
91
|
+
"src/shared/components/ui/sonner.tsx",
|
|
92
|
+
]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 6. TDD: RED Phase
|
|
96
|
+
|
|
97
|
+
Write ALL test files first — zero implementation code at this stage:
|
|
98
|
+
- `__tests__/<component>.test.tsx` — component tests
|
|
99
|
+
- `__tests__/use-<feature>.test.ts` — hook tests (if applicable)
|
|
100
|
+
- `__tests__/<action>.action.test.ts` — action tests (see `references/server-action-test-template.md`)
|
|
101
|
+
- `__tests__/<feature>.queries.test.ts` — query tests
|
|
102
|
+
|
|
103
|
+
Import mock helpers from `@/shared/test-utils/supabase-mock` — do not inline mock chains.
|
|
104
|
+
|
|
105
|
+
**Coverage guidance:** Threshold is 95% statements/functions/lines and 90% branches. Use `/* v8 ignore next */` for genuinely unreachable defensive branches.
|
|
106
|
+
|
|
107
|
+
**For standard CRUD features**: write tests and implementation per module (queries test+impl → actions test+impl → UI test+impl) instead of strict all-RED-then-all-GREEN.
|
|
108
|
+
|
|
109
|
+
**BLOCKING GATE — do not proceed until this passes:**
|
|
110
|
+
Run `pnpm test:unit` and paste the output. All new tests must **FAIL** (red). If any new test passes without implementation, the test is wrong — fix it before continuing.
|
|
111
|
+
|
|
112
|
+
### 7. TDD: GREEN Phase
|
|
113
|
+
|
|
114
|
+
Follow the **Parallel Execution Plan** from the requirements file:
|
|
115
|
+
|
|
116
|
+
- **Group A**: run first (foundation — schema, shared types, no dependencies)
|
|
117
|
+
- **Group B**: launch `backend` and `frontend` subagents **in parallel** after Group A completes
|
|
118
|
+
- **Group C**: run after Group B (integration, polish)
|
|
119
|
+
|
|
120
|
+
Typical assignment:
|
|
121
|
+
|
|
122
|
+
| backend handles | frontend handles |
|
|
123
|
+
|-----------------|-------------------|
|
|
124
|
+
| `actions/` | `components/` |
|
|
125
|
+
| `queries/` | `page.tsx` |
|
|
126
|
+
| `schema.ts` changes | shadcn component installs |
|
|
127
|
+
| RLS policies | Loading/empty states |
|
|
128
|
+
|
|
129
|
+
**Do NOT run intermediate verification between sub-tasks.** Complete all GREEN phase work, then run a single verification pass in Step 9.
|
|
130
|
+
|
|
131
|
+
**BLOCKING GATE — do not proceed until this passes:**
|
|
132
|
+
Run `pnpm test:unit` and paste the output. All tests must **PASS** (green).
|
|
133
|
+
|
|
134
|
+
### 8. Refactor
|
|
135
|
+
|
|
136
|
+
Clean up while keeping tests green.
|
|
137
|
+
|
|
138
|
+
### 9. Verify & Self-Check
|
|
139
|
+
|
|
140
|
+
Run all three **together** (single pass) and paste output:
|
|
141
|
+
```bash
|
|
142
|
+
pnpm test:coverage # Must meet thresholds: 95% statements/functions/lines, 90% branches
|
|
143
|
+
pnpm lint # Must pass with 0 warnings
|
|
144
|
+
pnpm typecheck # Must pass with 0 errors
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
If issues are found, fix **all** of them, then run the full trio again once.
|
|
148
|
+
|
|
149
|
+
Before moving to the review gate, verify manually:
|
|
150
|
+
- [ ] No `any` types: `grep -r ": any" src/features/<feature>/`
|
|
151
|
+
- [ ] No `eslint-disable` comments
|
|
152
|
+
- [ ] All UI text in Spanish, all `it()`/`describe()` text in English
|
|
153
|
+
- [ ] No file over 200 lines
|
|
154
|
+
- [ ] No function over 20 lines
|
|
155
|
+
- [ ] AAA pattern (`// Arrange`, `// Act`, `// Assert`) in every test
|
|
156
|
+
- [ ] `ActionResult` returned from every Server Action mutation
|
|
157
|
+
- [ ] Toast feedback (`toast.success` / `toast.error`) for every mutation
|
|
158
|
+
- [ ] RLS policies written for every new table
|
|
159
|
+
|
|
160
|
+
### 10. Review Gate
|
|
161
|
+
|
|
162
|
+
Run @code-reviewer and @security-researcher **in parallel** on the changed files. Fix any findings before marking the feature complete.
|
|
163
|
+
|
|
164
|
+
This step is not optional. The feature is not done until both reviewers pass.
|
|
165
|
+
|
|
166
|
+
> **Note for time-critical work:** if the user explicitly says to skip the review gate, you may proceed — but flag the skipped step so it can be done as a follow-up.
|
|
167
|
+
|
|
168
|
+
### 11. Update Architecture Snapshot & MCP Memory
|
|
169
|
+
|
|
170
|
+
Update `.cursor/memory/architecture-snapshot.md`:
|
|
171
|
+
- Add new DB tables to the schema table
|
|
172
|
+
- Add new shadcn components to the installed list
|
|
173
|
+
- Add the new feature to the Existing Features table
|
|
174
|
+
- Add any new canonical pattern references that differ from existing ones
|
|
175
|
+
|
|
176
|
+
Then run `/memory sync` to persist all changes to MCP memory so future sessions load context efficiently.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Common Issues
|
|
181
|
+
|
|
182
|
+
| Problem | Cause | Fix |
|
|
183
|
+
|---------|-------|-----|
|
|
184
|
+
| MCP memory returns no results | Memory not synced yet | Run `/memory sync` first, then retry |
|
|
185
|
+
| Test passes without implementation (RED fails) | Test asserts on falsy/default values | Ensure the test expects a specific truthy result that only a real implementation can produce |
|
|
186
|
+
| `pnpm typecheck` fails on schema file | Schema imported in test without mock | Add the schema file to `coverage.exclude` in `vitest.config.ts` |
|
|
187
|
+
| Context exceeds 60% mid-build | Feature too large for one conversation | Stop, note the current step, start a new conversation referencing it |
|
|
188
|
+
| `lint-staged` blocks commit | ESLint warnings treated as errors | Fix all warnings; never use `eslint-disable` inline — fix the code |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Guardrails
|
|
193
|
+
- NEVER start implementation before the user approves the plan (Step 3)
|
|
194
|
+
- NEVER start implementation before the RED gate is confirmed with actual test output
|
|
195
|
+
- Coverage threshold: 95% statements/functions/lines, 90% branches
|
|
196
|
+
- Follow `features/* → shared/*` dependency direction
|
|
197
|
+
- RLS is mandatory for every new table — not a post-step
|
|
198
|
+
- Write lint-compliant code from the start — read the Strict Rules Reference before the first line of code
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Server Action Testing Pattern
|
|
2
|
+
|
|
3
|
+
Test Server Actions by mocking `createClient`, asserting on the returned `ActionResult`, and verifying side effects.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { vi, it, expect, describe, beforeEach } from "vitest";
|
|
9
|
+
import { createClient } from "@/shared/lib/supabase/server";
|
|
10
|
+
import { revalidatePath } from "next/cache";
|
|
11
|
+
import { makeChain, makeSupabaseMock } from "@/shared/test-utils/supabase-mock";
|
|
12
|
+
import { myAction } from "@/features/example/actions/my.action";
|
|
13
|
+
|
|
14
|
+
vi.mock("@/shared/lib/supabase/server", () => ({ createClient: vi.fn() }));
|
|
15
|
+
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Unauthenticated case (required in every action test)
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
it("returns error when user is not authenticated", async () => {
|
|
26
|
+
// Arrange
|
|
27
|
+
const { mockClient } = makeSupabaseMock({ user: null });
|
|
28
|
+
vi.mocked(createClient).mockResolvedValue(mockClient as never);
|
|
29
|
+
const formData = new FormData();
|
|
30
|
+
|
|
31
|
+
// Act
|
|
32
|
+
const result = await myAction(formData);
|
|
33
|
+
|
|
34
|
+
// Assert
|
|
35
|
+
expect(result).toEqual({ status: "error", message: "No autenticado" });
|
|
36
|
+
expect(revalidatePath).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Happy path (insert example)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
it("returns success and revalidates on valid input", async () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
const { mockClient, mockFrom } = makeSupabaseMock({ user: { id: "user-1" } });
|
|
46
|
+
vi.mocked(createClient).mockResolvedValue(mockClient as never);
|
|
47
|
+
mockFrom.mockReturnValue(makeChain({ error: null }));
|
|
48
|
+
const formData = new FormData();
|
|
49
|
+
formData.set("title", "My item");
|
|
50
|
+
|
|
51
|
+
// Act
|
|
52
|
+
const result = await myAction(formData);
|
|
53
|
+
|
|
54
|
+
// Assert
|
|
55
|
+
expect(result).toEqual({ status: "success", message: expect.any(String) });
|
|
56
|
+
expect(revalidatePath).toHaveBeenCalledWith("/");
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## DB error case
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
it("returns error when db insert fails", async () => {
|
|
64
|
+
// Arrange
|
|
65
|
+
const { mockClient, mockFrom } = makeSupabaseMock({ user: { id: "user-1" } });
|
|
66
|
+
vi.mocked(createClient).mockResolvedValue(mockClient as never);
|
|
67
|
+
mockFrom.mockReturnValue(makeChain({ error: { message: "db error" } }));
|
|
68
|
+
const formData = new FormData();
|
|
69
|
+
formData.set("title", "My item");
|
|
70
|
+
|
|
71
|
+
// Act
|
|
72
|
+
const result = await myAction(formData);
|
|
73
|
+
|
|
74
|
+
// Assert
|
|
75
|
+
expect(result).toEqual({ status: "error", message: expect.any(String) });
|
|
76
|
+
expect(revalidatePath).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Validation error case
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
it("returns error when required field is missing", async () => {
|
|
84
|
+
// Arrange
|
|
85
|
+
const { mockClient } = makeSupabaseMock({ user: { id: "user-1" } });
|
|
86
|
+
vi.mocked(createClient).mockResolvedValue(mockClient as never);
|
|
87
|
+
const formData = new FormData(); // no fields set
|
|
88
|
+
|
|
89
|
+
// Act
|
|
90
|
+
const result = await myAction(formData);
|
|
91
|
+
|
|
92
|
+
// Assert
|
|
93
|
+
expect(result).toEqual({ status: "error", message: expect.any(String) });
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Key Rules
|
|
98
|
+
|
|
99
|
+
- Always import `makeChain` and `makeSupabaseMock` from `@/shared/test-utils/supabase-mock`
|
|
100
|
+
- Every action test must cover: unauthenticated, validation error, DB error, happy path
|
|
101
|
+
- `mockFrom` returns a chain — each chained method (`.select()`, `.insert()`, etc.) returns the same chain object
|
|
102
|
+
- `then` on the chain resolves with the value you pass to `makeChain()`
|
|
103
|
+
- Never use `mockReturnThis()` — it does not match Supabase's actual chaining API
|