330 lines
11 KiB
Swift
330 lines
11 KiB
Swift
import XCTest
|
|
|
|
final class OpenClimbTests: XCTestCase {
|
|
|
|
override func setUpWithError() throws {
|
|
}
|
|
|
|
override func tearDownWithError() throws {
|
|
}
|
|
|
|
// MARK: - Data Validation Tests
|
|
|
|
func testDifficultyGradeComparison() throws {
|
|
// Test basic difficulty grade string comparison
|
|
let grade1 = "V5"
|
|
let grade2 = "V3"
|
|
let grade3 = "V5"
|
|
|
|
XCTAssertEqual(grade1, grade3)
|
|
XCTAssertNotEqual(grade1, grade2)
|
|
XCTAssertFalse(grade1.isEmpty)
|
|
}
|
|
|
|
func testClimbTypeValidation() throws {
|
|
// Test climb type validation
|
|
let validClimbTypes = ["ROPE", "BOULDER"]
|
|
|
|
for climbType in validClimbTypes {
|
|
XCTAssertTrue(validClimbTypes.contains(climbType))
|
|
XCTAssertFalse(climbType.isEmpty)
|
|
}
|
|
|
|
let invalidTypes = ["", "unknown", "invalid", "sport", "trad", "toprope"]
|
|
for invalidType in invalidTypes {
|
|
if !invalidType.isEmpty {
|
|
XCTAssertFalse(validClimbTypes.contains(invalidType))
|
|
}
|
|
}
|
|
}
|
|
|
|
func testDateFormatting() throws {
|
|
// Test ISO 8601 date formatting
|
|
let formatter = ISO8601DateFormatter()
|
|
let date = Date()
|
|
let formattedDate = formatter.string(from: date)
|
|
|
|
XCTAssertFalse(formattedDate.isEmpty)
|
|
XCTAssertTrue(formattedDate.contains("T"))
|
|
XCTAssertTrue(formattedDate.hasSuffix("Z"))
|
|
|
|
// Test parsing back
|
|
let parsedDate = formatter.date(from: formattedDate)
|
|
XCTAssertNotNil(parsedDate)
|
|
}
|
|
|
|
func testSessionDurationCalculation() throws {
|
|
// Test session duration calculation
|
|
let startTime = Date()
|
|
let endTime = Date(timeInterval: 3600, since: startTime) // 1 hour later
|
|
let duration = endTime.timeIntervalSince(startTime)
|
|
|
|
XCTAssertEqual(duration, 3600, accuracy: 1.0)
|
|
XCTAssertGreaterThan(duration, 0)
|
|
}
|
|
|
|
func testAttemptResultValidation() throws {
|
|
// Test attempt result validation
|
|
let validResults = ["completed", "failed", "flash", "project"]
|
|
|
|
for result in validResults {
|
|
XCTAssertTrue(validResults.contains(result))
|
|
XCTAssertFalse(result.isEmpty)
|
|
}
|
|
}
|
|
|
|
func testGymCreation() throws {
|
|
// Test gym model creation with basic validation
|
|
let gymName = "Test Climbing Gym"
|
|
let location = "Test City"
|
|
let supportedTypes = ["BOULDER", "ROPE"]
|
|
|
|
XCTAssertFalse(gymName.isEmpty)
|
|
XCTAssertFalse(location.isEmpty)
|
|
XCTAssertFalse(supportedTypes.isEmpty)
|
|
XCTAssertEqual(supportedTypes.count, 2)
|
|
XCTAssertTrue(supportedTypes.contains("BOULDER"))
|
|
XCTAssertTrue(supportedTypes.contains("ROPE"))
|
|
}
|
|
|
|
func testProblemValidation() throws {
|
|
// Test problem model validation
|
|
let problemName = "Test Problem"
|
|
let climbType = "BOULDER"
|
|
let difficulty = "V5"
|
|
let tags = ["overhang", "crimpy"]
|
|
|
|
XCTAssertFalse(problemName.isEmpty)
|
|
XCTAssertTrue(["BOULDER", "ROPE"].contains(climbType))
|
|
XCTAssertFalse(difficulty.isEmpty)
|
|
XCTAssertEqual(tags.count, 2)
|
|
XCTAssertTrue(tags.allSatisfy { !$0.isEmpty })
|
|
}
|
|
|
|
func testSessionStatusTransitions() throws {
|
|
// Test session status transitions
|
|
let validStatuses = ["planned", "active", "completed", "cancelled"]
|
|
|
|
for status in validStatuses {
|
|
XCTAssertTrue(validStatuses.contains(status))
|
|
XCTAssertFalse(status.isEmpty)
|
|
}
|
|
|
|
// Test status transitions logic
|
|
let initialStatus = "planned"
|
|
let activeStatus = "active"
|
|
let completedStatus = "completed"
|
|
|
|
XCTAssertNotEqual(initialStatus, activeStatus)
|
|
XCTAssertNotEqual(activeStatus, completedStatus)
|
|
}
|
|
|
|
func testUniqueIDGeneration() throws {
|
|
// Test unique ID generation using UUID
|
|
let id1 = UUID().uuidString
|
|
let id2 = UUID().uuidString
|
|
|
|
XCTAssertNotEqual(id1, id2)
|
|
XCTAssertFalse(id1.isEmpty)
|
|
XCTAssertFalse(id2.isEmpty)
|
|
XCTAssertEqual(id1.count, 36) // UUID string length
|
|
XCTAssertTrue(id1.contains("-"))
|
|
}
|
|
|
|
func testDataValidation() throws {
|
|
// Test basic data validation patterns
|
|
let emptyString = ""
|
|
let validString = "test"
|
|
let negativeNumber = -1
|
|
let positiveNumber = 5
|
|
let zeroNumber = 0
|
|
|
|
XCTAssertTrue(emptyString.isEmpty)
|
|
XCTAssertFalse(validString.isEmpty)
|
|
XCTAssertLessThan(negativeNumber, 0)
|
|
XCTAssertGreaterThan(positiveNumber, 0)
|
|
XCTAssertEqual(zeroNumber, 0)
|
|
}
|
|
|
|
// MARK: - Collection Tests
|
|
|
|
func testArrayOperations() throws {
|
|
// Test array operations for climb data
|
|
var problems: [String] = []
|
|
|
|
XCTAssertTrue(problems.isEmpty)
|
|
XCTAssertEqual(problems.count, 0)
|
|
|
|
problems.append("Problem 1")
|
|
problems.append("Problem 2")
|
|
|
|
XCTAssertFalse(problems.isEmpty)
|
|
XCTAssertEqual(problems.count, 2)
|
|
XCTAssertTrue(problems.contains("Problem 1"))
|
|
|
|
let filteredProblems = problems.filter { $0.contains("1") }
|
|
XCTAssertEqual(filteredProblems.count, 1)
|
|
}
|
|
|
|
func testDictionaryOperations() throws {
|
|
// Test dictionary operations for data storage
|
|
var gymData: [String: Any] = [:]
|
|
|
|
XCTAssertTrue(gymData.isEmpty)
|
|
|
|
gymData["name"] = "Test Gym"
|
|
gymData["location"] = "Test City"
|
|
gymData["types"] = ["BOULDER", "ROPE"]
|
|
|
|
XCTAssertFalse(gymData.isEmpty)
|
|
XCTAssertEqual(gymData.count, 3)
|
|
XCTAssertNotNil(gymData["name"])
|
|
|
|
if let name = gymData["name"] as? String {
|
|
XCTAssertEqual(name, "Test Gym")
|
|
} else {
|
|
XCTFail("Failed to cast gym name to String")
|
|
}
|
|
}
|
|
|
|
// MARK: - String and Numeric Tests
|
|
|
|
func testStringManipulation() throws {
|
|
// Test string operations common in climb data
|
|
let problemName = " Test Problem V5 "
|
|
let trimmedName = problemName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let uppercaseName = trimmedName.uppercased()
|
|
let lowercaseName = trimmedName.lowercased()
|
|
|
|
XCTAssertEqual(trimmedName, "Test Problem V5")
|
|
XCTAssertEqual(uppercaseName, "TEST PROBLEM V5")
|
|
XCTAssertEqual(lowercaseName, "test problem v5")
|
|
|
|
let components = trimmedName.components(separatedBy: " ")
|
|
XCTAssertEqual(components.count, 3)
|
|
XCTAssertEqual(components.last, "V5")
|
|
}
|
|
|
|
func testNumericOperations() throws {
|
|
// Test numeric operations for climb ratings and statistics
|
|
let grades = [3, 5, 7, 4, 6]
|
|
let sum = grades.reduce(0, +)
|
|
let average = Double(sum) / Double(grades.count)
|
|
let maxGrade = grades.max() ?? 0
|
|
let minGrade = grades.min() ?? 0
|
|
|
|
XCTAssertEqual(sum, 25)
|
|
XCTAssertEqual(average, 5.0, accuracy: 0.01)
|
|
XCTAssertEqual(maxGrade, 7)
|
|
XCTAssertEqual(minGrade, 3)
|
|
}
|
|
|
|
// MARK: - JSON and Data Format Tests
|
|
|
|
func testJSONSerialization() throws {
|
|
// Test JSON serialization for basic data structures
|
|
let testData: [String: Any] = [
|
|
"id": "test123",
|
|
"name": "Test Gym",
|
|
"active": true,
|
|
"rating": 4.5,
|
|
"types": ["BOULDER", "ROPE"],
|
|
]
|
|
|
|
XCTAssertNoThrow({
|
|
let jsonData = try JSONSerialization.data(withJSONObject: testData)
|
|
XCTAssertFalse(jsonData.isEmpty)
|
|
|
|
let deserializedData =
|
|
try JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
|
|
XCTAssertNotNil(deserializedData)
|
|
XCTAssertEqual(deserializedData?["name"] as? String, "Test Gym")
|
|
})
|
|
}
|
|
|
|
func testDateSerialization() throws {
|
|
// Test date serialization for API compatibility
|
|
let date = Date()
|
|
let formatter = ISO8601DateFormatter()
|
|
let dateString = formatter.string(from: date)
|
|
let parsedDate = formatter.date(from: dateString)
|
|
|
|
XCTAssertNotNil(parsedDate)
|
|
XCTAssertEqual(date.timeIntervalSince1970, parsedDate!.timeIntervalSince1970, accuracy: 1.0)
|
|
}
|
|
|
|
// MARK: - Active Session Preservation Tests
|
|
|
|
func testActiveSessionPreservationDuringImport() throws {
|
|
// Test that active sessions are preserved during import operations
|
|
// This tests the fix for the bug where active sessions disappear after sync
|
|
|
|
// Simulate an active session that exists locally but not in import data
|
|
let activeSessionId = UUID()
|
|
let gymId = UUID()
|
|
|
|
// Test data structure representing local active session
|
|
let localActiveSession: [String: Any] = [
|
|
"id": activeSessionId.uuidString,
|
|
"gymId": gymId.uuidString,
|
|
"status": "active",
|
|
"date": "2024-01-01",
|
|
"startTime": "2024-01-01T10:00:00Z",
|
|
]
|
|
|
|
// Test data structure representing server sessions (without the active one)
|
|
let serverSessions: [[String: Any]] = [
|
|
[
|
|
"id": UUID().uuidString,
|
|
"gymId": gymId.uuidString,
|
|
"status": "completed",
|
|
"date": "2023-12-31",
|
|
"startTime": "2023-12-31T15:00:00Z",
|
|
"endTime": "2023-12-31T17:00:00Z",
|
|
]
|
|
]
|
|
|
|
// Verify test setup
|
|
XCTAssertEqual(localActiveSession["status"] as? String, "active")
|
|
XCTAssertEqual(serverSessions.count, 1)
|
|
XCTAssertEqual(serverSessions[0]["status"] as? String, "completed")
|
|
|
|
// Verify that the active session ID is not in the server sessions
|
|
let serverSessionIds = serverSessions.compactMap { $0["id"] as? String }
|
|
XCTAssertFalse(serverSessionIds.contains(activeSessionId.uuidString))
|
|
|
|
// Test that we can identify an active session
|
|
if let status = localActiveSession["status"] as? String {
|
|
XCTAssertTrue(status == "active")
|
|
} else {
|
|
XCTFail("Failed to extract session status")
|
|
}
|
|
|
|
// Test session ID validation
|
|
if let sessionIdString = localActiveSession["id"] as? String,
|
|
let sessionId = UUID(uuidString: sessionIdString)
|
|
{
|
|
XCTAssertEqual(sessionId, activeSessionId)
|
|
} else {
|
|
XCTFail("Failed to parse session ID")
|
|
}
|
|
|
|
// Test that combining sessions preserves both local active and server completed
|
|
var combinedSessions = serverSessions
|
|
combinedSessions.append(localActiveSession)
|
|
|
|
XCTAssertEqual(combinedSessions.count, 2)
|
|
|
|
// Verify both session types are present
|
|
let hasActiveSession = combinedSessions.contains { session in
|
|
(session["status"] as? String) == "active"
|
|
}
|
|
let hasCompletedSession = combinedSessions.contains { session in
|
|
(session["status"] as? String) == "completed"
|
|
}
|
|
|
|
XCTAssertTrue(hasActiveSession, "Combined sessions should contain active session")
|
|
XCTAssertTrue(hasCompletedSession, "Combined sessions should contain completed session")
|
|
}
|
|
}
|