1.2.2 - "Bug fixes and improvements"
This commit is contained in:
479
sync/format_test.go
Normal file
479
sync/format_test.go
Normal file
@@ -0,0 +1,479 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDataFormatCompatibility(t *testing.T) {
|
||||
t.Run("JSON Marshaling and Unmarshaling", func(t *testing.T) {
|
||||
originalBackup := ClimbDataBackup{
|
||||
ExportedAt: "2024-01-01T10:00:00Z",
|
||||
Version: "2.0",
|
||||
FormatVersion: "2.0",
|
||||
Gyms: []BackupGym{
|
||||
{
|
||||
ID: "gym1",
|
||||
Name: "Test Gym",
|
||||
Location: stringPtr("Test Location"),
|
||||
SupportedClimbTypes: []string{"BOULDER", "ROPE"},
|
||||
DifficultySystems: []string{"V", "YDS"},
|
||||
CustomDifficultyGrades: []string{"V0+", "V1+"},
|
||||
Notes: stringPtr("Test notes"),
|
||||
CreatedAt: "2024-01-01T10:00:00Z",
|
||||
UpdatedAt: "2024-01-01T10:00:00Z",
|
||||
},
|
||||
},
|
||||
Problems: []BackupProblem{
|
||||
{
|
||||
ID: "problem1",
|
||||
GymID: "gym1",
|
||||
Name: stringPtr("Test Problem"),
|
||||
Description: stringPtr("A challenging problem"),
|
||||
ClimbType: "BOULDER",
|
||||
Difficulty: DifficultyGrade{
|
||||
System: "V",
|
||||
Grade: "V5",
|
||||
NumericValue: 5,
|
||||
},
|
||||
Tags: []string{"overhang", "crimpy"},
|
||||
Location: stringPtr("Wall A"),
|
||||
ImagePaths: []string{"image1.jpg", "image2.jpg"},
|
||||
IsActive: true,
|
||||
DateSet: stringPtr("2024-01-01"),
|
||||
Notes: stringPtr("Watch the start"),
|
||||
CreatedAt: "2024-01-01T10:00:00Z",
|
||||
UpdatedAt: "2024-01-01T10:00:00Z",
|
||||
},
|
||||
},
|
||||
Sessions: []BackupClimbSession{
|
||||
{
|
||||
ID: "session1",
|
||||
GymID: "gym1",
|
||||
Date: "2024-01-01",
|
||||
StartTime: stringPtr("2024-01-01T10:00:00Z"),
|
||||
EndTime: stringPtr("2024-01-01T12:00:00Z"),
|
||||
Duration: int64Ptr(7200),
|
||||
Status: "completed",
|
||||
Notes: stringPtr("Great session"),
|
||||
CreatedAt: "2024-01-01T10:00:00Z",
|
||||
UpdatedAt: "2024-01-01T12:00:00Z",
|
||||
},
|
||||
},
|
||||
Attempts: []BackupAttempt{
|
||||
{
|
||||
ID: "attempt1",
|
||||
SessionID: "session1",
|
||||
ProblemID: "problem1",
|
||||
Result: "completed",
|
||||
HighestHold: stringPtr("Top"),
|
||||
Notes: stringPtr("Clean send"),
|
||||
Duration: int64Ptr(300),
|
||||
RestTime: int64Ptr(120),
|
||||
Timestamp: "2024-01-01T10:30:00Z",
|
||||
CreatedAt: "2024-01-01T10:30:00Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(originalBackup)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal backup: %v", err)
|
||||
}
|
||||
|
||||
var unmarshaledBackup ClimbDataBackup
|
||||
if err := json.Unmarshal(jsonData, &unmarshaledBackup); err != nil {
|
||||
t.Fatalf("Failed to unmarshal backup: %v", err)
|
||||
}
|
||||
|
||||
if originalBackup.Version != unmarshaledBackup.Version {
|
||||
t.Errorf("Version mismatch: expected %s, got %s", originalBackup.Version, unmarshaledBackup.Version)
|
||||
}
|
||||
|
||||
if len(originalBackup.Gyms) != len(unmarshaledBackup.Gyms) {
|
||||
t.Errorf("Gyms count mismatch: expected %d, got %d", len(originalBackup.Gyms), len(unmarshaledBackup.Gyms))
|
||||
}
|
||||
|
||||
if len(originalBackup.Problems) != len(unmarshaledBackup.Problems) {
|
||||
t.Errorf("Problems count mismatch: expected %d, got %d", len(originalBackup.Problems), len(unmarshaledBackup.Problems))
|
||||
}
|
||||
|
||||
if len(originalBackup.Sessions) != len(unmarshaledBackup.Sessions) {
|
||||
t.Errorf("Sessions count mismatch: expected %d, got %d", len(originalBackup.Sessions), len(unmarshaledBackup.Sessions))
|
||||
}
|
||||
|
||||
if len(originalBackup.Attempts) != len(unmarshaledBackup.Attempts) {
|
||||
t.Errorf("Attempts count mismatch: expected %d, got %d", len(originalBackup.Attempts), len(unmarshaledBackup.Attempts))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Required Fields Validation", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
jsonInput string
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
name: "Valid minimal backup",
|
||||
jsonInput: `{
|
||||
"exportedAt": "2024-01-01T10:00:00Z",
|
||||
"version": "2.0",
|
||||
"formatVersion": "2.0",
|
||||
"gyms": [],
|
||||
"problems": [],
|
||||
"sessions": [],
|
||||
"attempts": []
|
||||
}`,
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Missing version field",
|
||||
jsonInput: `{
|
||||
"exportedAt": "2024-01-01T10:00:00Z",
|
||||
"formatVersion": "2.0",
|
||||
"gyms": [],
|
||||
"problems": [],
|
||||
"sessions": [],
|
||||
"attempts": []
|
||||
}`,
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON structure",
|
||||
jsonInput: `{
|
||||
"exportedAt": "2024-01-01T10:00:00Z",
|
||||
"version": "2.0",
|
||||
"formatVersion": "2.0",
|
||||
"gyms": "not an array"
|
||||
}`,
|
||||
shouldError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var backup ClimbDataBackup
|
||||
err := json.Unmarshal([]byte(tc.jsonInput), &backup)
|
||||
|
||||
if tc.shouldError && err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
|
||||
if !tc.shouldError && err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Difficulty Grade Format", func(t *testing.T) {
|
||||
testGrades := []DifficultyGrade{
|
||||
{System: "V", Grade: "V0", NumericValue: 0},
|
||||
{System: "V", Grade: "V5", NumericValue: 5},
|
||||
{System: "V", Grade: "V10", NumericValue: 10},
|
||||
{System: "YDS", Grade: "5.10a", NumericValue: 100},
|
||||
{System: "YDS", Grade: "5.12d", NumericValue: 124},
|
||||
{System: "Font", Grade: "6A", NumericValue: 60},
|
||||
{System: "Custom", Grade: "Beginner", NumericValue: 1},
|
||||
}
|
||||
|
||||
for _, grade := range testGrades {
|
||||
jsonData, err := json.Marshal(grade)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to marshal grade %+v: %v", grade, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var unmarshaledGrade DifficultyGrade
|
||||
if err := json.Unmarshal(jsonData, &unmarshaledGrade); err != nil {
|
||||
t.Errorf("Failed to unmarshal grade %s: %v", string(jsonData), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if grade.System != unmarshaledGrade.System {
|
||||
t.Errorf("System mismatch for grade %+v: expected %s, got %s", grade, grade.System, unmarshaledGrade.System)
|
||||
}
|
||||
|
||||
if grade.Grade != unmarshaledGrade.Grade {
|
||||
t.Errorf("Grade mismatch for grade %+v: expected %s, got %s", grade, grade.Grade, unmarshaledGrade.Grade)
|
||||
}
|
||||
|
||||
if grade.NumericValue != unmarshaledGrade.NumericValue {
|
||||
t.Errorf("NumericValue mismatch for grade %+v: expected %d, got %d", grade, grade.NumericValue, unmarshaledGrade.NumericValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Null and Optional Fields", func(t *testing.T) {
|
||||
jsonWithNulls := `{
|
||||
"exportedAt": "2024-01-01T10:00:00Z",
|
||||
"version": "2.0",
|
||||
"formatVersion": "2.0",
|
||||
"gyms": [{
|
||||
"id": "gym1",
|
||||
"name": "Test Gym",
|
||||
"location": null,
|
||||
"supportedClimbTypes": ["boulder"],
|
||||
"difficultySystems": ["V"],
|
||||
"customDifficultyGrades": [],
|
||||
"notes": null,
|
||||
"createdAt": "2024-01-01T10:00:00Z",
|
||||
"updatedAt": "2024-01-01T10:00:00Z"
|
||||
}],
|
||||
"problems": [{
|
||||
"id": "problem1",
|
||||
"gymId": "gym1",
|
||||
"name": null,
|
||||
"description": null,
|
||||
"climbType": "boulder",
|
||||
"difficulty": {
|
||||
"system": "V",
|
||||
"grade": "V5",
|
||||
"numericValue": 5
|
||||
},
|
||||
"tags": [],
|
||||
"location": null,
|
||||
"imagePaths": [],
|
||||
"isActive": true,
|
||||
"dateSet": null,
|
||||
"notes": null,
|
||||
"createdAt": "2024-01-01T10:00:00Z",
|
||||
"updatedAt": "2024-01-01T10:00:00Z"
|
||||
}],
|
||||
"sessions": [],
|
||||
"attempts": []
|
||||
}`
|
||||
|
||||
var backup ClimbDataBackup
|
||||
if err := json.Unmarshal([]byte(jsonWithNulls), &backup); err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON with nulls: %v", err)
|
||||
}
|
||||
|
||||
if backup.Gyms[0].Location != nil {
|
||||
t.Error("Expected location to be nil")
|
||||
}
|
||||
|
||||
if backup.Gyms[0].Notes != nil {
|
||||
t.Error("Expected notes to be nil")
|
||||
}
|
||||
|
||||
if backup.Problems[0].Name != nil {
|
||||
t.Error("Expected problem name to be nil")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Date Format Validation", func(t *testing.T) {
|
||||
validDates := []string{
|
||||
"2024-01-01T10:00:00Z",
|
||||
"2024-12-31T23:59:59Z",
|
||||
"2024-06-15T12:30:45Z",
|
||||
"2024-01-01T00:00:00Z",
|
||||
}
|
||||
|
||||
invalidDates := []string{
|
||||
"2024-01-01 10:00:00",
|
||||
"2024/01/01T10:00:00Z",
|
||||
"2024-1-1T10:00:00Z",
|
||||
}
|
||||
|
||||
for _, date := range validDates {
|
||||
if !isValidISODate(date) {
|
||||
t.Errorf("Valid date %s was marked as invalid", date)
|
||||
}
|
||||
}
|
||||
|
||||
for _, date := range invalidDates {
|
||||
if isValidISODate(date) {
|
||||
t.Errorf("Invalid date %s was marked as valid", date)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Field Length Limits", func(t *testing.T) {
|
||||
longString := strings.Repeat("a", 10000)
|
||||
|
||||
gym := BackupGym{
|
||||
ID: "gym1",
|
||||
Name: longString,
|
||||
Location: &longString,
|
||||
SupportedClimbTypes: []string{"boulder"},
|
||||
DifficultySystems: []string{"V"},
|
||||
CustomDifficultyGrades: []string{},
|
||||
Notes: &longString,
|
||||
CreatedAt: "2024-01-01T10:00:00Z",
|
||||
UpdatedAt: "2024-01-01T10:00:00Z",
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(gym)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to marshal gym with long strings: %v", err)
|
||||
}
|
||||
|
||||
var unmarshaledGym BackupGym
|
||||
if err := json.Unmarshal(jsonData, &unmarshaledGym); err != nil {
|
||||
t.Errorf("Failed to unmarshal gym with long strings: %v", err)
|
||||
}
|
||||
|
||||
if unmarshaledGym.Name != longString {
|
||||
t.Error("Long name was not preserved")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Array Field Validation", func(t *testing.T) {
|
||||
backup := ClimbDataBackup{
|
||||
ExportedAt: "2024-01-01T10:00:00Z",
|
||||
Version: "2.0",
|
||||
FormatVersion: "2.0",
|
||||
Gyms: nil,
|
||||
Problems: []BackupProblem{},
|
||||
Sessions: []BackupClimbSession{},
|
||||
Attempts: []BackupAttempt{},
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(backup)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal backup with nil gyms: %v", err)
|
||||
}
|
||||
|
||||
var unmarshaledBackup ClimbDataBackup
|
||||
if err := json.Unmarshal(jsonData, &unmarshaledBackup); err != nil {
|
||||
t.Fatalf("Failed to unmarshal backup with nil gyms: %v", err)
|
||||
}
|
||||
|
||||
if len(unmarshaledBackup.Gyms) != 0 {
|
||||
t.Error("Expected gyms to be empty or nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func isValidISODate(date string) bool {
|
||||
// More robust ISO date validation
|
||||
if !strings.Contains(date, "T") || !strings.HasSuffix(date, "Z") {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check basic format: YYYY-MM-DDTHH:MM:SSZ
|
||||
parts := strings.Split(date, "T")
|
||||
if len(parts) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
datePart := parts[0]
|
||||
timePart := strings.TrimSuffix(parts[1], "Z")
|
||||
|
||||
// Date part should be YYYY-MM-DD
|
||||
dateComponents := strings.Split(datePart, "-")
|
||||
if len(dateComponents) != 3 || len(dateComponents[0]) != 4 || len(dateComponents[1]) != 2 || len(dateComponents[2]) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Time part should be HH:MM:SS
|
||||
timeComponents := strings.Split(timePart, ":")
|
||||
if len(timeComponents) != 3 || len(timeComponents[0]) != 2 || len(timeComponents[1]) != 2 || len(timeComponents[2]) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func TestVersionCompatibility(t *testing.T) {
|
||||
testCases := []struct {
|
||||
version string
|
||||
formatVersion string
|
||||
shouldSupport bool
|
||||
}{
|
||||
{"2.0", "2.0", true},
|
||||
{"1.0", "1.0", true},
|
||||
{"2.1", "2.0", false},
|
||||
{"3.0", "2.0", false},
|
||||
{"1.0", "2.0", false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.version+"/"+tc.formatVersion, func(t *testing.T) {
|
||||
backup := ClimbDataBackup{
|
||||
Version: tc.version,
|
||||
FormatVersion: tc.formatVersion,
|
||||
}
|
||||
|
||||
// Only exact version matches are supported for now
|
||||
isSupported := backup.Version == "2.0" && backup.FormatVersion == "2.0"
|
||||
if backup.Version == "1.0" && backup.FormatVersion == "1.0" {
|
||||
isSupported = true
|
||||
}
|
||||
|
||||
if isSupported != tc.shouldSupport {
|
||||
t.Errorf("Version %s support expectation mismatch: expected %v, got %v",
|
||||
tc.version, tc.shouldSupport, isSupported)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClimbTypeValidation(t *testing.T) {
|
||||
validClimbTypes := []string{"boulder", "sport", "trad", "toprope", "aid", "ice", "mixed"}
|
||||
invalidClimbTypes := []string{"", "invalid", "BOULDER", "Sport", "unknown"}
|
||||
|
||||
for _, climbType := range validClimbTypes {
|
||||
if !isValidClimbType(climbType) {
|
||||
t.Errorf("Valid climb type %s was marked as invalid", climbType)
|
||||
}
|
||||
}
|
||||
|
||||
for _, climbType := range invalidClimbTypes {
|
||||
if isValidClimbType(climbType) {
|
||||
t.Errorf("Invalid climb type %s was marked as valid", climbType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isValidClimbType(climbType string) bool {
|
||||
validTypes := map[string]bool{
|
||||
"boulder": true,
|
||||
"sport": true,
|
||||
"trad": true,
|
||||
"toprope": true,
|
||||
"aid": true,
|
||||
"ice": true,
|
||||
"mixed": true,
|
||||
}
|
||||
return validTypes[climbType]
|
||||
}
|
||||
|
||||
func TestAttemptResultValidation(t *testing.T) {
|
||||
validResults := []string{"completed", "failed", "flash", "project", "attempt"}
|
||||
invalidResults := []string{"", "invalid", "COMPLETED", "Failed", "unknown"}
|
||||
|
||||
for _, result := range validResults {
|
||||
if !isValidAttemptResult(result) {
|
||||
t.Errorf("Valid attempt result %s was marked as invalid", result)
|
||||
}
|
||||
}
|
||||
|
||||
for _, result := range invalidResults {
|
||||
if isValidAttemptResult(result) {
|
||||
t.Errorf("Invalid attempt result %s was marked as valid", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for creating pointers
|
||||
func stringPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func int64Ptr(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
func isValidAttemptResult(result string) bool {
|
||||
validResults := map[string]bool{
|
||||
"completed": true,
|
||||
"failed": true,
|
||||
"flash": true,
|
||||
"project": true,
|
||||
"attempt": true,
|
||||
}
|
||||
return validResults[result]
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
module openclimb-sync
|
||||
|
||||
go 1.25
|
||||
go 1.21
|
||||
|
||||
361
sync/main_test.go
Normal file
361
sync/main_test.go
Normal file
@@ -0,0 +1,361 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSyncServerAuthentication(t *testing.T) {
|
||||
server := &SyncServer{authToken: "test-token"}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
expected bool
|
||||
}{
|
||||
{"Valid token", "test-token", true},
|
||||
{"Invalid token", "wrong-token", false},
|
||||
{"Empty token", "", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Test the authentication logic directly without HTTP
|
||||
result := strings.Compare(tt.token, server.authToken) == 0
|
||||
if result != tt.expected {
|
||||
t.Errorf("authenticate() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDataNonExistentFile(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
server := &SyncServer{
|
||||
dataFile: filepath.Join(tempDir, "nonexistent.json"),
|
||||
}
|
||||
|
||||
backup, err := server.loadData()
|
||||
if err != nil {
|
||||
t.Errorf("loadData() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
if backup == nil {
|
||||
t.Error("Expected backup to be non-nil")
|
||||
}
|
||||
|
||||
if len(backup.Gyms) != 0 || len(backup.Problems) != 0 || len(backup.Sessions) != 0 || len(backup.Attempts) != 0 {
|
||||
t.Error("Expected empty backup data")
|
||||
}
|
||||
|
||||
if backup.Version != "2.0" || backup.FormatVersion != "2.0" {
|
||||
t.Error("Expected version and format version to be 2.0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveAndLoadData(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
server := &SyncServer{
|
||||
dataFile: filepath.Join(tempDir, "test.json"),
|
||||
imagesDir: filepath.Join(tempDir, "images"),
|
||||
}
|
||||
|
||||
testData := &ClimbDataBackup{
|
||||
Version: "2.0",
|
||||
FormatVersion: "2.0",
|
||||
Gyms: []BackupGym{
|
||||
{
|
||||
ID: "gym1",
|
||||
Name: "Test Gym",
|
||||
},
|
||||
},
|
||||
Problems: []BackupProblem{
|
||||
{
|
||||
ID: "problem1",
|
||||
GymID: "gym1",
|
||||
ClimbType: "BOULDER",
|
||||
Difficulty: DifficultyGrade{
|
||||
System: "V",
|
||||
Grade: "V5",
|
||||
NumericValue: 5,
|
||||
},
|
||||
IsActive: true,
|
||||
},
|
||||
},
|
||||
Sessions: []BackupClimbSession{},
|
||||
Attempts: []BackupAttempt{},
|
||||
}
|
||||
|
||||
err := server.saveData(testData)
|
||||
if err != nil {
|
||||
t.Errorf("saveData() error = %v", err)
|
||||
}
|
||||
|
||||
loadedData, err := server.loadData()
|
||||
if err != nil {
|
||||
t.Errorf("loadData() error = %v", err)
|
||||
}
|
||||
|
||||
if len(loadedData.Gyms) != 1 || loadedData.Gyms[0].ID != "gym1" {
|
||||
t.Error("Loaded gym data doesn't match saved data")
|
||||
}
|
||||
|
||||
if len(loadedData.Problems) != 1 || loadedData.Problems[0].ID != "problem1" {
|
||||
t.Error("Loaded problem data doesn't match saved data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinFunction(t *testing.T) {
|
||||
tests := []struct {
|
||||
a, b, expected int
|
||||
}{
|
||||
{5, 3, 3},
|
||||
{2, 8, 2},
|
||||
{4, 4, 4},
|
||||
{0, 1, 0},
|
||||
{-1, 2, -1},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := min(tt.a, tt.b)
|
||||
if result != tt.expected {
|
||||
t.Errorf("min(%d, %d) = %d, want %d", tt.a, tt.b, result, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClimbDataBackupValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
backup ClimbDataBackup
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
name: "Valid backup",
|
||||
backup: ClimbDataBackup{
|
||||
Version: "2.0",
|
||||
FormatVersion: "2.0",
|
||||
Gyms: []BackupGym{},
|
||||
Problems: []BackupProblem{},
|
||||
Sessions: []BackupClimbSession{},
|
||||
Attempts: []BackupAttempt{},
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
name: "Missing version",
|
||||
backup: ClimbDataBackup{
|
||||
FormatVersion: "2.0",
|
||||
Gyms: []BackupGym{},
|
||||
Problems: []BackupProblem{},
|
||||
Sessions: []BackupClimbSession{},
|
||||
Attempts: []BackupAttempt{},
|
||||
},
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
name: "Missing format version",
|
||||
backup: ClimbDataBackup{
|
||||
Version: "2.0",
|
||||
Gyms: []BackupGym{},
|
||||
Problems: []BackupProblem{},
|
||||
Sessions: []BackupClimbSession{},
|
||||
Attempts: []BackupAttempt{},
|
||||
},
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Test basic validation logic
|
||||
hasVersion := tt.backup.Version != ""
|
||||
hasFormatVersion := tt.backup.FormatVersion != ""
|
||||
isValid := hasVersion && hasFormatVersion
|
||||
|
||||
if isValid != tt.isValid {
|
||||
t.Errorf("validation = %v, want %v", isValid, tt.isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupDataStructures(t *testing.T) {
|
||||
t.Run("BackupGym", func(t *testing.T) {
|
||||
gym := BackupGym{
|
||||
ID: "gym1",
|
||||
Name: "Test Gym",
|
||||
SupportedClimbTypes: []string{"BOULDER", "ROPE"},
|
||||
DifficultySystems: []string{"V", "YDS"},
|
||||
CustomDifficultyGrades: []string{},
|
||||
CreatedAt: "2024-01-01T10:00:00Z",
|
||||
UpdatedAt: "2024-01-01T10:00:00Z",
|
||||
}
|
||||
|
||||
if gym.ID != "gym1" {
|
||||
t.Errorf("Expected gym ID 'gym1', got %s", gym.ID)
|
||||
}
|
||||
|
||||
if len(gym.SupportedClimbTypes) != 2 {
|
||||
t.Errorf("Expected 2 climb types, got %d", len(gym.SupportedClimbTypes))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BackupProblem", func(t *testing.T) {
|
||||
problem := BackupProblem{
|
||||
ID: "problem1",
|
||||
GymID: "gym1",
|
||||
ClimbType: "BOULDER",
|
||||
Difficulty: DifficultyGrade{
|
||||
System: "V",
|
||||
Grade: "V5",
|
||||
NumericValue: 5,
|
||||
},
|
||||
IsActive: true,
|
||||
CreatedAt: "2024-01-01T10:00:00Z",
|
||||
UpdatedAt: "2024-01-01T10:00:00Z",
|
||||
}
|
||||
|
||||
if problem.ClimbType != "BOULDER" {
|
||||
t.Errorf("Expected climb type 'BOULDER', got %s", problem.ClimbType)
|
||||
}
|
||||
|
||||
if problem.Difficulty.Grade != "V5" {
|
||||
t.Errorf("Expected difficulty 'V5', got %s", problem.Difficulty.Grade)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDifficultyGrade(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
grade DifficultyGrade
|
||||
expectedGrade string
|
||||
expectedValue int
|
||||
}{
|
||||
{
|
||||
name: "V-Scale grade",
|
||||
grade: DifficultyGrade{
|
||||
System: "V",
|
||||
Grade: "V5",
|
||||
NumericValue: 5,
|
||||
},
|
||||
expectedGrade: "V5",
|
||||
expectedValue: 5,
|
||||
},
|
||||
{
|
||||
name: "YDS grade",
|
||||
grade: DifficultyGrade{
|
||||
System: "YDS",
|
||||
Grade: "5.10a",
|
||||
NumericValue: 10,
|
||||
},
|
||||
expectedGrade: "5.10a",
|
||||
expectedValue: 10,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.grade.Grade != tt.expectedGrade {
|
||||
t.Errorf("Expected grade %s, got %s", tt.expectedGrade, tt.grade.Grade)
|
||||
}
|
||||
|
||||
if tt.grade.NumericValue != tt.expectedValue {
|
||||
t.Errorf("Expected numeric value %d, got %d", tt.expectedValue, tt.grade.NumericValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONSerialization(t *testing.T) {
|
||||
backup := ClimbDataBackup{
|
||||
Version: "2.0",
|
||||
FormatVersion: "2.0",
|
||||
Gyms: []BackupGym{
|
||||
{
|
||||
ID: "gym1",
|
||||
Name: "Test Gym",
|
||||
},
|
||||
},
|
||||
Problems: []BackupProblem{
|
||||
{
|
||||
ID: "problem1",
|
||||
GymID: "gym1",
|
||||
ClimbType: "BOULDER",
|
||||
Difficulty: DifficultyGrade{
|
||||
System: "V",
|
||||
Grade: "V5",
|
||||
NumericValue: 5,
|
||||
},
|
||||
IsActive: true,
|
||||
},
|
||||
},
|
||||
Sessions: []BackupClimbSession{},
|
||||
Attempts: []BackupAttempt{},
|
||||
}
|
||||
|
||||
// Test JSON marshaling
|
||||
jsonData, err := json.Marshal(backup)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to marshal JSON: %v", err)
|
||||
}
|
||||
|
||||
// Test JSON unmarshaling
|
||||
var unmarshaledBackup ClimbDataBackup
|
||||
err = json.Unmarshal(jsonData, &unmarshaledBackup)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
if unmarshaledBackup.Version != backup.Version {
|
||||
t.Errorf("Version mismatch after JSON round-trip")
|
||||
}
|
||||
|
||||
if len(unmarshaledBackup.Gyms) != len(backup.Gyms) {
|
||||
t.Errorf("Gyms count mismatch after JSON round-trip")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampHandling(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
timestamp := now.Format(time.RFC3339)
|
||||
|
||||
// Test that timestamp is in correct format
|
||||
parsedTime, err := time.Parse(time.RFC3339, timestamp)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse timestamp: %v", err)
|
||||
}
|
||||
|
||||
if parsedTime.Year() != now.Year() {
|
||||
t.Errorf("Year mismatch in timestamp")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilePathHandling(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
isValid bool
|
||||
}{
|
||||
{"Valid filename", "test.json", true},
|
||||
{"Valid path", filepath.Join(tempDir, "data.json"), true},
|
||||
{"Empty filename", "", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isEmpty := tt.filename == ""
|
||||
isValid := !isEmpty
|
||||
|
||||
if isValid != tt.isValid {
|
||||
t.Errorf("File path validation = %v, want %v", isValid, tt.isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user