feat: implement Iteration 0 foundation (backend + Flutter client)
Backend (Go): - Project structure with chi router, pgxpool, goose migrations - JWT auth (access/refresh tokens) with Firebase token verification - NoopTokenVerifier for local dev without Firebase credentials - PostgreSQL user repository with atomic profile updates (transactions) - Mifflin-St Jeor calorie calculation based on profile data - REST API: POST /auth/login, /auth/refresh, /auth/logout, GET/PUT /profile, GET /health - Middleware: auth, CORS (localhost wildcard), logging, recovery, request_id - Unit tests (51 passing) and integration tests (testcontainers) - Docker Compose setup with postgres healthcheck and graceful shutdown Flutter client: - Riverpod state management with GoRouter navigation - Firebase Auth (email/password + Google sign-in with web popup support) - Platform-aware API URLs (web/Android/iOS) - Dio HTTP client with JWT auth interceptor and concurrent refresh handling - Secure token storage - Screens: Login, Register, Home (tabs: Menu, Recipes, Products, Profile) - Unit tests (17 passing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
73
backend/internal/user/calories_test.go
Normal file
73
backend/internal/user/calories_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func TestCalculateDailyCalories_MaleMaintain(t *testing.T) {
|
||||
cal := CalculateDailyCalories(ptr(180), ptr(80.0), ptr(30), ptr("male"), ptr("moderate"), ptr("maintain"))
|
||||
if cal == nil {
|
||||
t.Fatal("expected non-nil result")
|
||||
}
|
||||
// BMR = 10*80 + 6.25*180 - 5*30 + 5 = 800 + 1125 - 150 + 5 = 1780
|
||||
// TDEE = 1780 * 1.55 = 2759
|
||||
if *cal != 2759 {
|
||||
t.Errorf("expected 2759, got %d", *cal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDailyCalories_FemaleLose(t *testing.T) {
|
||||
cal := CalculateDailyCalories(ptr(165), ptr(60.0), ptr(25), ptr("female"), ptr("low"), ptr("lose"))
|
||||
if cal == nil {
|
||||
t.Fatal("expected non-nil result")
|
||||
}
|
||||
// BMR = 10*60 + 6.25*165 - 5*25 - 161 = 600 + 1031.25 - 125 - 161 = 1345.25
|
||||
// TDEE = 1345.25 * 1.375 = 1849.72
|
||||
// Goal: -500 = 1349.72 → 1350
|
||||
if *cal != 1350 {
|
||||
t.Errorf("expected 1350, got %d", *cal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDailyCalories_MaleGain(t *testing.T) {
|
||||
cal := CalculateDailyCalories(ptr(175), ptr(70.0), ptr(28), ptr("male"), ptr("high"), ptr("gain"))
|
||||
if cal == nil {
|
||||
t.Fatal("expected non-nil result")
|
||||
}
|
||||
// BMR = 10*70 + 6.25*175 - 5*28 + 5 = 700 + 1093.75 - 140 + 5 = 1658.75
|
||||
// TDEE = 1658.75 * 1.725 = 2861.34
|
||||
// Goal: +300 = 3161.34 → 3161
|
||||
if *cal != 3161 {
|
||||
t.Errorf("expected 3161, got %d", *cal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDailyCalories_NilHeight(t *testing.T) {
|
||||
cal := CalculateDailyCalories(nil, ptr(80.0), ptr(30), ptr("male"), ptr("moderate"), ptr("maintain"))
|
||||
if cal != nil {
|
||||
t.Fatal("expected nil when height is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDailyCalories_NilWeight(t *testing.T) {
|
||||
cal := CalculateDailyCalories(ptr(180), nil, ptr(30), ptr("male"), ptr("moderate"), ptr("maintain"))
|
||||
if cal != nil {
|
||||
t.Fatal("expected nil when weight is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDailyCalories_InvalidGender(t *testing.T) {
|
||||
cal := CalculateDailyCalories(ptr(180), ptr(80.0), ptr(30), ptr("other"), ptr("moderate"), ptr("maintain"))
|
||||
if cal != nil {
|
||||
t.Fatal("expected nil for invalid gender")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDailyCalories_InvalidActivity(t *testing.T) {
|
||||
cal := CalculateDailyCalories(ptr(180), ptr(80.0), ptr(30), ptr("male"), ptr("extreme"), ptr("maintain"))
|
||||
if cal != nil {
|
||||
t.Fatal("expected nil for invalid activity")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user