package fitparser import ( "fmt" "os" "testing" ) func TestIsFIT(t *testing.T) { tests := []struct { name string data []byte expected bool }{ { name: "Valid FIT signature", data: []byte{0x0E, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, '.', 'F', 'I', 'T', 0x00, 0x00}, expected: true, }, { name: "Invalid signature", data: []byte{0x0E, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'N', 'O', 'T', 'F', 0x00, 0x00}, expected: false, }, { name: "Too short", data: []byte{0x0E, 0x10}, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := IsFIT(tt.data) if result != tt.expected { t.Errorf("IsFIT() = %v, want %v", result, tt.expected) } }) } } func TestCalculateCRC(t *testing.T) { // Test with known CRC values data := []byte{0x0E, 0x10, 0xD9, 0x07, 0x00, 0x00, 0x00, 0x00, '.', 'F', 'I', 'T'} crc := CalculateCRC(data) // CRC should be non-zero for this data if crc == 0 { t.Errorf("CalculateCRC() returned 0, expected non-zero value") } // Test that same data produces same CRC crc2 := CalculateCRC(data) if crc != crc2 { t.Errorf("CalculateCRC() not deterministic: got %d and %d", crc, crc2) } } func TestParseHeader(t *testing.T) { tests := []struct { name string data []byte wantErr bool }{ { name: "Valid 12-byte header", data: []byte{0x0C, 0x10, 0xD9, 0x07, 0x10, 0x00, 0x00, 0x00, '.', 'F', 'I', 'T'}, wantErr: false, }, { name: "Too short", data: []byte{0x0C, 0x10}, wantErr: true, }, { name: "Invalid signature", data: []byte{0x0C, 0x10, 0xD9, 0x07, 0x10, 0x00, 0x00, 0x00, 'N', 'O', 'P', 'E'}, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { header, err := ParseHeader(tt.data) if (err != nil) != tt.wantErr { t.Errorf("ParseHeader() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr && header == nil { t.Errorf("ParseHeader() returned nil header without error") } }) } } func TestDecodeActivityFile(t *testing.T) { // Test with a real FIT file from the SDK examples filePath := "../example/testdata/Activity.fit" data, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Error reading test file %s: %v", filePath, err) } decoder, err := NewDecoder(data) if err != nil { t.Fatalf("NewDecoder() error = %v", err) } messages, err := decoder.Decode() if err != nil { t.Fatalf("Decode() error = %v", err) } if len(messages) == 0 { t.Error("Decode() returned no messages") } // Print some statistics t.Logf("Successfully decoded %d messages", len(messages)) // Count message types msgCounts := make(map[uint16]int) for _, msg := range messages { msgCounts[msg.Num]++ } t.Logf("Message type breakdown:") for mesgNum, count := range msgCounts { t.Logf(" %s (%d): %d messages", GetMessageName(mesgNum), mesgNum, count) } // Verify we have essential messages if msgCounts[MesgNumFileId] == 0 { t.Error("Expected at least one FileId message") } if msgCounts[MesgNumRecord] == 0 { t.Error("Expected at least one Record message") } } func TestDecodeSettingsFile(t *testing.T) { filePath := "../example/testdata/Settings.fit" data, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Error reading test file %s: %v", filePath, err) } decoder, err := NewDecoder(data) if err != nil { t.Fatalf("NewDecoder() error = %v", err) } messages, err := decoder.Decode() if err != nil { t.Fatalf("Decode() error = %v", err) } if len(messages) == 0 { t.Error("Decode() returned no messages") } t.Logf("Successfully decoded %d messages from Settings.fit", len(messages)) } func TestCheckIntegrity(t *testing.T) { filePath := "../example/testdata/Activity.fit" data, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Error reading test file %s: %v", filePath, err) } err = CheckIntegrity(data) if err != nil { t.Errorf("CheckIntegrity() error = %v", err) } } func TestConvertSemicirclesToDegrees(t *testing.T) { tests := []struct { semicircles int32 expected float64 }{ {0, 0.0}, {2147483647, 180.0}, // Max positive (approximately) {-2147483648, -180.0}, // Max negative (approximately) {1073741824, 90.0}, // Quarter circle } for _, tt := range tests { t.Run(fmt.Sprintf("%d_semicircles", tt.semicircles), func(t *testing.T) { result := ConvertSemicirclesToDegrees(tt.semicircles) // Allow small floating point error if result < tt.expected-0.1 || result > tt.expected+0.1 { t.Errorf("ConvertSemicirclesToDegrees(%d) = %f, want %f", tt.semicircles, result, tt.expected) } }) } } func BenchmarkDecode(b *testing.B) { filePath := "../example/testdata/Activity.fit" data, err := os.ReadFile(filePath) if err != nil { b.Fatalf("Error reading test file %s: %v", filePath, err) } b.ResetTimer() for i := 0; i < b.N; i++ { decoder, err := NewDecoder(data) if err != nil { b.Fatalf("NewDecoder() error = %v", err) } _, err = decoder.Decode() if err != nil { b.Fatalf("Decode() error = %v", err) } } } func BenchmarkDecodeNoCRC(b *testing.B) { filePath := "../example/testdata/Activity.fit" data, err := os.ReadFile(filePath) if err != nil { b.Fatalf("Error reading test file %s: %v", filePath, err) } b.ResetTimer() for i := 0; i < b.N; i++ { decoder, err := NewDecoder(data) if err != nil { b.Fatalf("NewDecoder() error = %v", err) } decoder.EnableCRCCheck(false) _, err = decoder.Decode() if err != nil { b.Fatalf("Decode() error = %v", err) } } }