RefreshDatabase + sqlite :memory: feature suites, frontend vitest + happy-dom scrub mocks, and CI gates that run pint before artisan test.phpunit.xml Harnessfullstack/Backend/phpunit.xml:5-45 defines two suites (Unit tests/Unit, Feature tests/Feature) and forces a hermetic env:<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="CACHE_STORE" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="MAIL_MAILER" value="array"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="GROQ_API_KEY" value="gsk-test-mock-key"/>sqlite :memory: + RefreshDatabase trait (tests/Feature/Commerce/PaymentFlowTest.php:11, AuthThrottleTest.php:7, etc.) migrates fresh per test class — no MySQL container, sub-second boot.CACHE_STORE array makes Cache::lock and Cache::remember (AI quota, webhook concurrency) synchronous.QUEUE_CONNECTION sync forces ShouldQueue listeners (FulfillOrderListener, queue:work in prod) to run inline — PaymentSucceeded fulfillment is testable without a worker.GROQ_API_KEY gsk-test-mock-key satisfies GroqService without external calls; AiQuotaCacheHitTest, AiRateLimitTest mock the provider.tests/Feature/** 52 + tests/Unit 2). By domain: Account (auth throttle, blocked user, verification gate), Catalog (destinations/hotels etc.), Commerce (CheckoutAbuse, PaymentFlow, Paymob cycle/timeout, Concurrency, Plans, Subscription), Trips (AI quota/rate, trip attach/fork), System (contacts, survey, reports, weather). grep shows every feature test uses RefreshDatabase.AuthThrottleTest hits RateLimiter::for('login') per IP+email, WeatherAbuseTest, CheckoutAbuseTest (SEC-08) assert 429 after 5/min.PaymobTimeoutTest, ConcurrencyTest assert findReusableCheckout + idempotency_key unique: double POST with same key returns one order.PaymobPaymentCycleTest, PaymentFlowTest assert verifyWebhook HMAC, Cache::lock, grace-period, and FulfillOrderListener idempotency provider_ref exists → abort.AiQuotaCacheHitTest asserts Cache::remember closure not called on hit (no quota burn), AiQuotaIntegrationTest asserts atomic where ai_generations_count < limit update.SurveyValidationTest, PlansTest assert 422 validation_errors flat array {field,message} (Phase 8 shape).itinera-showcase-react Vitestvite.config.ts:7-11 — test.environment happy-dom, globals true. No MSW, no network: showcase is static (Phase 9).npm.cmd test):src/pages/LifecyclePage.test.tsx — scrub-engine behavior (not DOM snapshot).src/components/lifecycle/ChapterScene.test.tsx — SVG scene prop rendering.LifecyclePage.test.tsx:14-120)ScrollTrigger needs a scroll container; tests replace ../lib/gsap with a synthetic engine:vi.mock("../lib/gsap", () => {
const makeTl = (vars) => {
const tl = { from: vi.fn(()=>tl), to: vi.fn(()=>tl), fromTo: vi.fn(()=>tl) }
const stv = vars?.scrollTrigger
if (stv) {
const start = 4000 + tlVars.length*7
tl.scrollTrigger = { ...stv, start, end:start+480, kill: vi.fn() }
if (stv.pin===true) fakePinStarts.push(start) // pin start synthetic
}
return tl
}
return { gsap:{ timeline:vi.fn(...), to:vi.fn((t,vars)=>{ if("scrollTop" in vars) (t as HTMLElement).scrollTop = vars.scrollTop }), set:vi.fn(), context:vi.fn(cb=>{cb();return{revert:vi.fn()}})}, ScrollTrigger:{ defaults:vi.fn(), create:vi.fn((v)=>{stConfigs.push(v); return{kill:vi.fn(),start:4000}}), refresh:vi.fn()} }
})pin:true, scrub:0.45.refreshStarts priority: fakePinStarts sort b.animation!=null wins over plain top center.goTo uses startOffset.get(id) vs fallback rect delta.wrapAnimate is gsap.to(scroller,{scrollTop}) synchronous in mock.?motion=reduced → zero pins, top center triggers only.npm run build (tsc -b && vite build) + oxlint + vitest run must pass. tasks/todo.md marks all R-tasks green before merge..github/workflows/ci.yml:9-47fullstack/Backend working dir, setup-php@2 php 8.2:lint: pint --test (fails on style drift).test: cp .env.example .env && artisan key:generate && artisan jwt:secret --force && artisan test — boots sqlite memory, runs all 54 suites with array cache/queue, GROQ_API_KEY mock.push: [main, develop, feat/community-hub] + pull_request: [main, develop] — no direct-to-main merge without both jobs green.sqlite :memory: + RefreshDatabase gives per-test isolation without containers; BCRYPT_ROUNDS 4 keeps factory users cheap.GROQ_API_KEY and Cache STORE array are mocked; CheckoutService, WebhookService, AiUsageService atomic UPDATE WHERE are exercised real.gsap.context + ScrollTrigger.create are replaced with start synthetics so refreshStarts priority and goTo math are testable without a layout engine.pint --test fails the pipeline — style drift blocks merge same as a failing assertion.artisan test is the contract. 106 route:list operations (Phase 7) are reachable only through RefreshDatabase suites; if a FormRequest rule changes, the 422 flat bag assertion fails.