hatch-surf/internal/face/performance_test.go
Chris Anderson 5bce1bd39b
Some checks are pending
CI / go (push) Waiting to run
CI / docker (push) Waiting to run
CI / lint-go (push) Waiting to run
CI / lint-docs (push) Waiting to run
CI / check-paperclip (push) Waiting to run
Phase 2.4: Testing & Deployment
- Wire face service into main.go with FACE_SERVICE_ENABLED toggle
- Add integration tests for AWS Rekognition (build tag: integration)
- Add performance benchmarks for face operations (build tag: performance)
- Fix PhotoRepository interface to use store types directly
- Remove duplicate PhotoInfo/FaceEmbeddingInfo types from face package
- Add go.mod replace directive for local module resolution

Integration tests: go test -tags=integration ./internal/face/...
Performance tests: go test -tags=performance -bench=. ./internal/face/...
Standard tests: go test ./...
2026-06-26 05:50:07 +02:00

225 lines
5.8 KiB
Go

//go:build performance
package face
import (
"context"
"fmt"
"os"
"testing"
"time"
)
// BenchmarkFaceDetection benchmarks face detection performance
func BenchmarkFaceDetection(b *testing.B) {
if os.Getenv("AWS_REGION") == "" {
b.Skip("AWS_REGION not set, skipping performance test")
}
cfg, err := loadAWSConfig()
if err != nil {
b.Fatalf("failed to load AWS config: %v", err)
}
collectionID := os.Getenv("REKOGNITION_COLLECTION")
if collectionID == "" {
collectionID = "hatch-faces-perf"
}
client := NewRekognitionClient(cfg, collectionID)
ctx := context.Background()
// Load test image
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
if err != nil {
b.Fatalf("failed to load test image: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := client.DetectFaces(ctx, imageBytes)
if err != nil {
b.Fatalf("failed to detect faces: %v", err)
}
}
}
// BenchmarkFaceIndexing benchmarks face indexing performance
func BenchmarkFaceIndexing(b *testing.B) {
if os.Getenv("AWS_REGION") == "" {
b.Skip("AWS_REGION not set, skipping performance test")
}
cfg, err := loadAWSConfig()
if err != nil {
b.Fatalf("failed to load AWS config: %v", err)
}
collectionID := os.Getenv("REKOGNITION_COLLECTION")
if collectionID == "" {
collectionID = "hatch-faces-perf"
}
client := NewRekognitionClient(cfg, collectionID)
ctx := context.Background()
// Create collection
if err := client.CreateCollection(ctx, collectionID); err != nil {
b.Fatalf("failed to create collection: %v", err)
}
defer client.DeleteCollection(ctx, collectionID)
// Load test image
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
if err != nil {
b.Fatalf("failed to load test image: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
photoID := fmt.Sprintf("perf-test-%d", time.Now().UnixNano())
_, err := client.IndexFace(ctx, imageBytes, photoID)
if err != nil {
b.Fatalf("failed to index face: %v", err)
}
}
}
// BenchmarkFaceSearch benchmarks face search performance
func BenchmarkFaceSearch(b *testing.B) {
if os.Getenv("AWS_REGION") == "" {
b.Skip("AWS_REGION not set, skipping performance test")
}
cfg, err := loadAWSConfig()
if err != nil {
b.Fatalf("failed to load AWS config: %v", err)
}
collectionID := os.Getenv("REKOGNITION_COLLECTION")
if collectionID == "" {
collectionID = "hatch-faces-perf"
}
client := NewRekognitionClient(cfg, collectionID)
ctx := context.Background()
// Create collection
if err := client.CreateCollection(ctx, collectionID); err != nil {
b.Fatalf("failed to create collection: %v", err)
}
defer client.DeleteCollection(ctx, collectionID)
// Load and index test image
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
if err != nil {
b.Fatalf("failed to load test image: %v", err)
}
photoID := "perf-search-" + time.Now().Format("20060102150405")
_, err = client.IndexFace(ctx, imageBytes, photoID)
if err != nil {
b.Fatalf("failed to index face: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := client.SearchFacesByImage(ctx, imageBytes, 10, 80.0)
if err != nil {
b.Fatalf("failed to search faces: %v", err)
}
}
}
// TestPerformanceTargets validates performance targets
func TestPerformanceTargets(t *testing.T) {
if os.Getenv("AWS_REGION") == "" {
t.Skip("AWS_REGION not set, skipping performance test")
}
cfg, err := loadAWSConfig()
if err != nil {
t.Fatalf("failed to load AWS config: %v", err)
}
collectionID := os.Getenv("REKOGNITION_COLLECTION")
if collectionID == "" {
collectionID = "hatch-faces-perf"
}
client := NewRekognitionClient(cfg, collectionID)
ctx := context.Background()
// Create collection
if err := client.CreateCollection(ctx, collectionID); err != nil {
t.Fatalf("failed to create collection: %v", err)
}
defer client.DeleteCollection(ctx, collectionID)
// Load test image
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
if err != nil {
t.Fatalf("failed to load test image: %v", err)
}
// Test face detection latency
start := time.Now()
_, err = client.DetectFaces(ctx, imageBytes)
if err != nil {
t.Fatalf("failed to detect faces: %v", err)
}
detectionLatency := time.Since(start)
if detectionLatency > 2*time.Second {
t.Errorf("face detection latency exceeded 2 seconds: %v", detectionLatency)
}
t.Logf("Face detection latency: %v", detectionLatency)
// Test face indexing latency
start = time.Now()
photoID := "perf-target-" + time.Now().Format("20060102150405")
_, err = client.IndexFace(ctx, imageBytes, photoID)
if err != nil {
t.Fatalf("failed to index face: %v", err)
}
indexingLatency := time.Since(start)
if indexingLatency > 3*time.Second {
t.Errorf("face indexing latency exceeded 3 seconds: %v", indexingLatency)
}
t.Logf("Face indexing latency: %v", indexingLatency)
// Test face search latency
start = time.Now()
_, err = client.SearchFacesByImage(ctx, imageBytes, 10, 80.0)
if err != nil {
t.Fatalf("failed to search faces: %v", err)
}
searchLatency := time.Since(start)
if searchLatency > 2*time.Second {
t.Errorf("face search latency exceeded 2 seconds: %v", searchLatency)
}
t.Logf("Face search latency: %v", searchLatency)
// Test end-to-end latency
start = time.Now()
// Detect
_, err = client.DetectFaces(ctx, imageBytes)
if err != nil {
t.Fatalf("failed to detect faces: %v", err)
}
// Index
photoID = "perf-e2e-" + time.Now().Format("20060102150405")
_, err = client.IndexFace(ctx, imageBytes, photoID)
if err != nil {
t.Fatalf("failed to index face: %v", err)
}
// Search
_, err = client.SearchFacesByImage(ctx, imageBytes, 10, 80.0)
if err != nil {
t.Fatalf("failed to search faces: %v", err)
}
e2eLatency := time.Since(start)
if e2eLatency > 5*time.Second {
t.Errorf("end-to-end latency exceeded 5 seconds: %v", e2eLatency)
}
t.Logf("End-to-end latency: %v", e2eLatency)
}