- 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 ./...
281 lines
6.9 KiB
Go
281 lines
6.9 KiB
Go
//go:build integration
|
|
|
|
package face
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestIntegration_DetectFaces tests face detection with real AWS Rekognition
|
|
func TestIntegration_DetectFaces(t *testing.T) {
|
|
if os.Getenv("AWS_REGION") == "" {
|
|
t.Skip("AWS_REGION not set, skipping integration 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-test"
|
|
}
|
|
|
|
client := NewRekognitionClient(cfg, collectionID)
|
|
ctx := context.Background()
|
|
|
|
// Load test image
|
|
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
|
|
if err != nil {
|
|
t.Fatalf("failed to load test image: %v", err)
|
|
}
|
|
|
|
// Detect faces
|
|
detections, err := client.DetectFaces(ctx, imageBytes)
|
|
if err != nil {
|
|
t.Fatalf("failed to detect faces: %v", err)
|
|
}
|
|
|
|
if len(detections) == 0 {
|
|
t.Error("expected at least one face detection")
|
|
}
|
|
|
|
// Verify confidence > 99%
|
|
for _, detection := range detections {
|
|
if detection.Confidence < 99.0 {
|
|
t.Errorf("expected confidence > 99%%, got %f", detection.Confidence)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestIntegration_IndexFace tests face indexing with real AWS Rekognition
|
|
func TestIntegration_IndexFace(t *testing.T) {
|
|
if os.Getenv("AWS_REGION") == "" {
|
|
t.Skip("AWS_REGION not set, skipping integration 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-test"
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// Index face
|
|
photoID := "test-photo-" + time.Now().Format("20060102150405")
|
|
detection, err := client.IndexFace(ctx, imageBytes, photoID)
|
|
if err != nil {
|
|
t.Fatalf("failed to index face: %v", err)
|
|
}
|
|
|
|
if detection == nil {
|
|
t.Error("expected face detection, got nil")
|
|
}
|
|
|
|
if detection.Confidence < 99.0 {
|
|
t.Errorf("expected confidence > 99%%, got %f", detection.Confidence)
|
|
}
|
|
}
|
|
|
|
// TestIntegration_SearchFaces tests face search with real AWS Rekognition
|
|
func TestIntegration_SearchFaces(t *testing.T) {
|
|
if os.Getenv("AWS_REGION") == "" {
|
|
t.Skip("AWS_REGION not set, skipping integration 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-test"
|
|
}
|
|
|
|
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 and index test image
|
|
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
|
|
if err != nil {
|
|
t.Fatalf("failed to load test image: %v", err)
|
|
}
|
|
|
|
photoID := "test-photo-" + time.Now().Format("20060102150405")
|
|
_, err = client.IndexFace(ctx, imageBytes, photoID)
|
|
if err != nil {
|
|
t.Fatalf("failed to index face: %v", err)
|
|
}
|
|
|
|
// Search for the indexed face
|
|
matches, err := client.SearchFacesByImage(ctx, imageBytes, 10, 80.0)
|
|
if err != nil {
|
|
t.Fatalf("failed to search faces: %v", err)
|
|
}
|
|
|
|
if len(matches) == 0 {
|
|
t.Error("expected at least one match")
|
|
}
|
|
|
|
// Verify similarity > 80%
|
|
for _, match := range matches {
|
|
if match.Similarity < 80.0 {
|
|
t.Errorf("expected similarity > 80%%, got %f", match.Similarity)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestIntegration_EndToEnd tests complete face detection → index → search flow
|
|
func TestIntegration_EndToEnd(t *testing.T) {
|
|
if os.Getenv("AWS_REGION") == "" {
|
|
t.Skip("AWS_REGION not set, skipping integration test")
|
|
}
|
|
|
|
startTime := time.Now()
|
|
|
|
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-test"
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// Step 1: Detect faces
|
|
detections, err := client.DetectFaces(ctx, imageBytes)
|
|
if err != nil {
|
|
t.Fatalf("failed to detect faces: %v", err)
|
|
}
|
|
|
|
if len(detections) == 0 {
|
|
t.Fatal("no faces detected")
|
|
}
|
|
|
|
// Step 2: Index face
|
|
photoID := "e2e-test-" + time.Now().Format("20060102150405")
|
|
_, err = client.IndexFace(ctx, imageBytes, photoID)
|
|
if err != nil {
|
|
t.Fatalf("failed to index face: %v", err)
|
|
}
|
|
|
|
// Step 3: Search for the indexed face
|
|
matches, err := client.SearchFacesByImage(ctx, imageBytes, 10, 80.0)
|
|
if err != nil {
|
|
t.Fatalf("failed to search faces: %v", err)
|
|
}
|
|
|
|
if len(matches) == 0 {
|
|
t.Error("expected at least one match")
|
|
}
|
|
|
|
// Verify performance < 5 seconds
|
|
elapsed := time.Since(startTime)
|
|
if elapsed > 5*time.Second {
|
|
t.Errorf("end-to-end latency exceeded 5 seconds: %v", elapsed)
|
|
}
|
|
|
|
t.Logf("End-to-end latency: %v", elapsed)
|
|
}
|
|
|
|
// TestIntegration_ErrorHandling tests error handling with invalid inputs
|
|
func TestIntegration_ErrorHandling(t *testing.T) {
|
|
if os.Getenv("AWS_REGION") == "" {
|
|
t.Skip("AWS_REGION not set, skipping integration 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-test"
|
|
}
|
|
|
|
client := NewRekognitionClient(cfg, collectionID)
|
|
ctx := context.Background()
|
|
|
|
// Test with empty image data
|
|
_, err = client.DetectFaces(ctx, []byte{})
|
|
if err == nil {
|
|
t.Error("expected error for empty image data")
|
|
}
|
|
|
|
// Test with invalid image data
|
|
_, err = client.DetectFaces(ctx, []byte("invalid image data"))
|
|
if err == nil {
|
|
t.Error("expected error for invalid image data")
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func loadAWSConfig() (aws.Config, error) {
|
|
return config.LoadDefaultConfig(context.Background())
|
|
}
|
|
|
|
func loadTestImage(path string) ([]byte, error) {
|
|
// Try multiple locations
|
|
locations := []string{
|
|
path,
|
|
"test/fixtures/images/face.jpg",
|
|
"testdata/face.jpg",
|
|
"../test/fixtures/images/face.jpg",
|
|
}
|
|
|
|
for _, loc := range locations {
|
|
data, err := os.ReadFile(loc)
|
|
if err == nil {
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("test image not found in any of the expected locations")
|
|
} |