From c26c2bb7e59402893e927cde1907d8f0879278ed Mon Sep 17 00:00:00 2001 From: Mike Kell Date: Thu, 24 Jul 2025 22:15:43 -0400 Subject: [PATCH] initial tests for frant and back end --- backend/requirements-dev.txt | 3 + backend/tests/test_api.py | 93 ++++++++++++++++ frontend/complycore_flutter/pubspec.yaml | 2 + .../test/api_smoke_test.dart | 101 ++++++++++++++++++ .../complycore_flutter/test/widget_test.dart | 30 ------ 5 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 backend/requirements-dev.txt create mode 100644 backend/tests/test_api.py create mode 100644 frontend/complycore_flutter/test/api_smoke_test.dart delete mode 100644 frontend/complycore_flutter/test/widget_test.dart diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt new file mode 100644 index 0000000..7ea0050 --- /dev/null +++ b/backend/requirements-dev.txt @@ -0,0 +1,3 @@ +# backend/requirements-dev.txt +pytest +pytest-dotenv # loads .env before running tests (optional) diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py new file mode 100644 index 0000000..c0508c3 --- /dev/null +++ b/backend/tests/test_api.py @@ -0,0 +1,93 @@ +# backend/tests/test_api.py +""" +Simple smoke–test suite for the ComplyCore FastAPI backend. + +Run with: + poetry install --with dev # or `pip install -r requirements-dev.txt` + pytest backend/tests # or simply `pytest` at repo root +""" + +import os +from datetime import datetime, timedelta, timezone + +import jwt # PyJWT +import pytest +from fastapi.testclient import TestClient + +# ---- local import of your FastAPI app --------------------------------------- +from backend.main import app # adjusts if your main.py lives elsewhere +# ----------------------------------------------------------------------------- + + +client = TestClient(app) + +# --------------------------------------------------------------------------- # +# Helpers # +# --------------------------------------------------------------------------- # +def make_test_jwt( + user_id: str = "00000000-0000-0000-0000-000000000002", + email: str = "testuser@complycore.dev", + role: str = "authenticated", +) -> str: + """ + Craft a short-lived JWT signed with the same secret the API expects. + """ + secret = os.getenv("SUPABASE_JWT_SECRET", "NOT_SET") + if secret == "NOT_SET": + raise RuntimeError( + "SUPABASE_JWT_SECRET not loaded – copy backend/.env.example → " + ".env and set your secret before running the tests" + ) + + now = datetime.now(timezone.utc) + payload = { + "sub": user_id, + "aud": "authenticated", + "role": role, + "email": email, + "iat": int(now.timestamp()), + "exp": int((now + timedelta(minutes=10)).timestamp()), + "iss": "supabase", + "email_confirmed_at": now.isoformat(), + } + return jwt.encode(payload, secret, algorithm="HS256") + + +# --------------------------------------------------------------------------- # +# Tests # +# --------------------------------------------------------------------------- # +def test_healthz(): + r = client.get("/healthz") + assert r.status_code == 200 + assert r.json() == {"status": "ok"} + + +@pytest.mark.skipif( + os.getenv("CI") == "true", + reason="Requires network access to Supabase; skip on CI", +) +def test_supabase_check(): + """ + Only a connectivity smoke test – we don’t assert DB/user values because + those differ per environment, we just ensure *something* comes back. + """ + r = client.get("/supabase-check") + assert r.status_code == 200 + body = r.json() + assert body.get("status") == "connected" + assert "db" in body and "user" in body + + +def test_me_authorized(): + token = make_test_jwt() + r = client.get("/me", headers={"Authorization": f"Bearer {token}"}) + assert r.status_code == 200 + body = r.json() + assert body["id"] == "00000000-0000-0000-0000-000000000002" + assert body["email"] == "testuser@complycore.dev" + assert body["role"] == "authenticated" + + +def test_me_unauthorized(): + r = client.get("/me") # no token + assert r.status_code == 401 diff --git a/frontend/complycore_flutter/pubspec.yaml b/frontend/complycore_flutter/pubspec.yaml index c2b31b0..309457e 100644 --- a/frontend/complycore_flutter/pubspec.yaml +++ b/frontend/complycore_flutter/pubspec.yaml @@ -37,6 +37,8 @@ dependencies: supabase_flutter: ^2.9.1 flutter_dotenv: ^5.2.1 gotrue: ^2.13.0 + http: ^1.2.0 + dart_jsonwebtoken: ^2.12.1 dev_dependencies: flutter_test: diff --git a/frontend/complycore_flutter/test/api_smoke_test.dart b/frontend/complycore_flutter/test/api_smoke_test.dart new file mode 100644 index 0000000..856c010 --- /dev/null +++ b/frontend/complycore_flutter/test/api_smoke_test.dart @@ -0,0 +1,101 @@ +// frontend/complycore_flutter/test/api_smoke_test.dart +// +// Basic integration-style smoke tests that hit the running ComplyCore +// FastAPI backend from a Flutter test runner. +// +// Run with: +// +// flutter test --dart-define=SUPABASE_JWT_SECRET=your_secret_here +// +// …while the backend is already up on http://localhost:8000. +// +// If you need a different host/port, override at the top of this file +// or pass --dart-define=API_BASE_URL=http://YOUR_HOST:PORT +// + +import 'dart:convert'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; + +/// Base URL for the API the tests will hit. +/// Override at runtime with: +/// flutter test --dart-define=API_BASE_URL=http://192.168.1.50:8000 +const String _apiBaseUrl = const String.fromEnvironment( + 'API_BASE_URL', + defaultValue: 'http://localhost:8000', +); + +/// Build a short-lived JWT signed with the same secret the backend expects. +String _buildJwt({ + String userId = '00000000-0000-0000-0000-000000000002', + String email = 'testuser@complycore.dev', + String role = 'authenticated', +}) { + final secret = const String.fromEnvironment( + 'SUPABASE_JWT_SECRET', + defaultValue: 'NOT_SET', + ); + + if (secret == 'NOT_SET') { + throw StateError( + 'SUPABASE_JWT_SECRET not provided.\n' + 'Run tests with:\n' + ' flutter test --dart-define=SUPABASE_JWT_SECRET=your_secret_here', + ); + } + + final now = DateTime.now().toUtc(); + final jwt = JWT({ + 'sub': userId, + 'aud': 'authenticated', + 'role': role, + 'email': email, + 'iat': (now.millisecondsSinceEpoch / 1000).floor(), + 'exp': (now.add(const Duration(minutes: 10)).millisecondsSinceEpoch / 1000) + .floor(), + 'iss': 'supabase', + 'email_confirmed_at': now.toIso8601String(), + }); + + return jwt.sign(SecretKey(secret), algorithm: JWTAlgorithm.HS256); +} + +void main() { + group('ComplyCore API smoke tests (Flutter)', () { + test('GET /healthz → 200 {"status":"ok"}', () async { + final res = await http.get(Uri.parse('$_apiBaseUrl/healthz')); + expect(res.statusCode, 200); + expect(jsonDecode(res.body)['status'], 'ok'); + }); + + test( + 'GET /supabase-check → 200, status=="connected"', + () async { + final res = await http.get(Uri.parse('$_apiBaseUrl/supabase-check')); + expect(res.statusCode, 200); + final body = jsonDecode(res.body); + expect(body['status'], 'connected'); + }, + skip: const bool.fromEnvironment('CI', defaultValue: false), + ); // skip on CI + + test('GET /me with valid JWT → 200, correct email', () async { + final token = _buildJwt(); + final res = await http.get( + Uri.parse('$_apiBaseUrl/me'), + headers: {'Authorization': 'Bearer $token'}, + ); + + expect(res.statusCode, 200); + final body = jsonDecode(res.body) as Map; + expect(body['email'], 'testuser@complycore.dev'); + expect(body['role'], 'authenticated'); + }); + + test('GET /me without token → 401/403', () async { + final res = await http.get(Uri.parse('$_apiBaseUrl/me')); + expect(res.statusCode, anyOf(401, 403)); + }); + }); +} diff --git a/frontend/complycore_flutter/test/widget_test.dart b/frontend/complycore_flutter/test/widget_test.dart deleted file mode 100644 index fbc2dbe..0000000 --- a/frontend/complycore_flutter/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:complycore_flutter/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}