Makefile, linting, and formatting
This commit is contained in:
@@ -2,10 +2,10 @@ package com.atridad.ascently
|
||||
|
||||
import com.atridad.ascently.data.format.*
|
||||
import com.atridad.ascently.data.model.*
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class BusinessLogicTests {
|
||||
|
||||
@@ -21,11 +21,11 @@ class BusinessLogicTests {
|
||||
assertNull(session.duration)
|
||||
|
||||
val completedSession =
|
||||
session.copy(
|
||||
status = SessionStatus.COMPLETED,
|
||||
endTime = getCurrentTimestamp(),
|
||||
duration = 7200L
|
||||
)
|
||||
session.copy(
|
||||
status = SessionStatus.COMPLETED,
|
||||
endTime = getCurrentTimestamp(),
|
||||
duration = 7200L,
|
||||
)
|
||||
assertEquals(SessionStatus.COMPLETED, completedSession.status)
|
||||
assertNotNull(completedSession.endTime)
|
||||
assertNotNull(completedSession.duration)
|
||||
@@ -38,12 +38,12 @@ class BusinessLogicTests {
|
||||
val session = ClimbSession.create(gym.id)
|
||||
|
||||
val attempt =
|
||||
Attempt.create(
|
||||
sessionId = session.id,
|
||||
problemId = problem.id,
|
||||
result = AttemptResult.SUCCESS,
|
||||
notes = "Clean send!"
|
||||
)
|
||||
Attempt.create(
|
||||
sessionId = session.id,
|
||||
problemId = problem.id,
|
||||
result = AttemptResult.SUCCESS,
|
||||
notes = "Clean send!",
|
||||
)
|
||||
|
||||
assertEquals(session.id, attempt.sessionId)
|
||||
assertEquals(problem.id, attempt.problemId)
|
||||
@@ -76,12 +76,12 @@ class BusinessLogicTests {
|
||||
val problem2 = createTestProblem(gym.id)
|
||||
|
||||
val attempts =
|
||||
listOf(
|
||||
Attempt.create(session.id, problem1.id, AttemptResult.SUCCESS),
|
||||
Attempt.create(session.id, problem1.id, AttemptResult.FALL),
|
||||
Attempt.create(session.id, problem2.id, AttemptResult.FLASH),
|
||||
Attempt.create(session.id, problem2.id, AttemptResult.SUCCESS)
|
||||
)
|
||||
listOf(
|
||||
Attempt.create(session.id, problem1.id, AttemptResult.SUCCESS),
|
||||
Attempt.create(session.id, problem1.id, AttemptResult.FALL),
|
||||
Attempt.create(session.id, problem2.id, AttemptResult.FLASH),
|
||||
Attempt.create(session.id, problem2.id, AttemptResult.SUCCESS),
|
||||
)
|
||||
|
||||
val sessionStats = calculateSessionStatistics(session, attempts)
|
||||
|
||||
@@ -97,17 +97,17 @@ class BusinessLogicTests {
|
||||
val session = ClimbSession.create(gym.id)
|
||||
|
||||
val problems =
|
||||
listOf(
|
||||
createTestProblemWithGrade(gym.id, "V3"),
|
||||
createTestProblemWithGrade(gym.id, "V4"),
|
||||
createTestProblemWithGrade(gym.id, "V5"),
|
||||
createTestProblemWithGrade(gym.id, "V6")
|
||||
)
|
||||
listOf(
|
||||
createTestProblemWithGrade(gym.id, "V3"),
|
||||
createTestProblemWithGrade(gym.id, "V4"),
|
||||
createTestProblemWithGrade(gym.id, "V5"),
|
||||
createTestProblemWithGrade(gym.id, "V6"),
|
||||
)
|
||||
|
||||
val attempts =
|
||||
problems.map { problem ->
|
||||
Attempt.create(session.id, problem.id, AttemptResult.SUCCESS)
|
||||
}
|
||||
problems.map { problem ->
|
||||
Attempt.create(session.id, problem.id, AttemptResult.SUCCESS)
|
||||
}
|
||||
|
||||
val progression = calculateDifficultyProgression(attempts, problems)
|
||||
|
||||
@@ -123,17 +123,17 @@ class BusinessLogicTests {
|
||||
val problems = listOf(createTestProblem(gym.id), createTestProblem(gym.id))
|
||||
val session = ClimbSession.create(gym.id)
|
||||
val attempts =
|
||||
problems.map { problem ->
|
||||
Attempt.create(session.id, problem.id, AttemptResult.SUCCESS)
|
||||
}
|
||||
problems.map { problem ->
|
||||
Attempt.create(session.id, problem.id, AttemptResult.SUCCESS)
|
||||
}
|
||||
|
||||
val backup =
|
||||
createBackupData(
|
||||
gyms = listOf(gym),
|
||||
problems = problems,
|
||||
sessions = listOf(session),
|
||||
attempts = attempts
|
||||
)
|
||||
createBackupData(
|
||||
gyms = listOf(gym),
|
||||
problems = problems,
|
||||
sessions = listOf(session),
|
||||
attempts = attempts,
|
||||
)
|
||||
|
||||
validateBackupIntegrity(backup)
|
||||
|
||||
@@ -146,30 +146,30 @@ class BusinessLogicTests {
|
||||
@Test
|
||||
fun testClimbTypeCompatibilityRules() {
|
||||
val boulderGym =
|
||||
Gym(
|
||||
id = "boulder_gym",
|
||||
name = "Boulder Gym",
|
||||
location = "Boulder City",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE, DifficultySystem.FONT),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp()
|
||||
)
|
||||
Gym(
|
||||
id = "boulder_gym",
|
||||
name = "Boulder Gym",
|
||||
location = "Boulder City",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE, DifficultySystem.FONT),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp(),
|
||||
)
|
||||
|
||||
val ropeGym =
|
||||
Gym(
|
||||
id = "rope_gym",
|
||||
name = "Rope Gym",
|
||||
location = "Rope City",
|
||||
supportedClimbTypes = listOf(ClimbType.ROPE),
|
||||
difficultySystems = listOf(DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp()
|
||||
)
|
||||
Gym(
|
||||
id = "rope_gym",
|
||||
name = "Rope Gym",
|
||||
location = "Rope City",
|
||||
supportedClimbTypes = listOf(ClimbType.ROPE),
|
||||
difficultySystems = listOf(DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp(),
|
||||
)
|
||||
|
||||
// Boulder gym should support boulder problems with V-Scale
|
||||
assertTrue(isCompatibleClimbType(boulderGym, ClimbType.BOULDER, DifficultySystem.V_SCALE))
|
||||
@@ -197,26 +197,26 @@ class BusinessLogicTests {
|
||||
val session = ClimbSession.create(gym.id)
|
||||
|
||||
val attempts =
|
||||
listOf(
|
||||
createAttemptWithTimestamp(
|
||||
session.id,
|
||||
problem.id,
|
||||
"2024-01-01T10:00:00Z",
|
||||
AttemptResult.FALL
|
||||
),
|
||||
createAttemptWithTimestamp(
|
||||
session.id,
|
||||
problem.id,
|
||||
"2024-01-01T10:05:00Z",
|
||||
AttemptResult.FALL
|
||||
),
|
||||
createAttemptWithTimestamp(
|
||||
session.id,
|
||||
problem.id,
|
||||
"2024-01-01T10:10:00Z",
|
||||
AttemptResult.SUCCESS
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
createAttemptWithTimestamp(
|
||||
session.id,
|
||||
problem.id,
|
||||
"2024-01-01T10:00:00Z",
|
||||
AttemptResult.FALL,
|
||||
),
|
||||
createAttemptWithTimestamp(
|
||||
session.id,
|
||||
problem.id,
|
||||
"2024-01-01T10:05:00Z",
|
||||
AttemptResult.FALL,
|
||||
),
|
||||
createAttemptWithTimestamp(
|
||||
session.id,
|
||||
problem.id,
|
||||
"2024-01-01T10:10:00Z",
|
||||
AttemptResult.SUCCESS,
|
||||
),
|
||||
)
|
||||
|
||||
val sequence = AttemptSequence(attempts)
|
||||
|
||||
@@ -230,32 +230,32 @@ class BusinessLogicTests {
|
||||
@Test
|
||||
fun testGradeConsistencyValidation() {
|
||||
val validCombinations =
|
||||
listOf(
|
||||
Pair(ClimbType.BOULDER, DifficultySystem.V_SCALE),
|
||||
Pair(ClimbType.BOULDER, DifficultySystem.FONT),
|
||||
Pair(ClimbType.ROPE, DifficultySystem.YDS),
|
||||
Pair(ClimbType.BOULDER, DifficultySystem.CUSTOM),
|
||||
Pair(ClimbType.ROPE, DifficultySystem.CUSTOM)
|
||||
)
|
||||
listOf(
|
||||
Pair(ClimbType.BOULDER, DifficultySystem.V_SCALE),
|
||||
Pair(ClimbType.BOULDER, DifficultySystem.FONT),
|
||||
Pair(ClimbType.ROPE, DifficultySystem.YDS),
|
||||
Pair(ClimbType.BOULDER, DifficultySystem.CUSTOM),
|
||||
Pair(ClimbType.ROPE, DifficultySystem.CUSTOM),
|
||||
)
|
||||
|
||||
val invalidCombinations =
|
||||
listOf(
|
||||
Pair(ClimbType.BOULDER, DifficultySystem.YDS),
|
||||
Pair(ClimbType.ROPE, DifficultySystem.V_SCALE),
|
||||
Pair(ClimbType.ROPE, DifficultySystem.FONT)
|
||||
)
|
||||
listOf(
|
||||
Pair(ClimbType.BOULDER, DifficultySystem.YDS),
|
||||
Pair(ClimbType.ROPE, DifficultySystem.V_SCALE),
|
||||
Pair(ClimbType.ROPE, DifficultySystem.FONT),
|
||||
)
|
||||
|
||||
validCombinations.forEach { (climbType, difficultySystem) ->
|
||||
assertTrue(
|
||||
"$climbType should be compatible with $difficultySystem",
|
||||
isValidGradeCombination(climbType, difficultySystem)
|
||||
"$climbType should be compatible with $difficultySystem",
|
||||
isValidGradeCombination(climbType, difficultySystem),
|
||||
)
|
||||
}
|
||||
|
||||
invalidCombinations.forEach { (climbType, difficultySystem) ->
|
||||
assertFalse(
|
||||
"$climbType should not be compatible with $difficultySystem",
|
||||
isValidGradeCombination(climbType, difficultySystem)
|
||||
"$climbType should not be compatible with $difficultySystem",
|
||||
isValidGradeCombination(climbType, difficultySystem),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -276,11 +276,11 @@ class BusinessLogicTests {
|
||||
@Test
|
||||
fun testImagePathHandling() {
|
||||
val originalPaths =
|
||||
listOf(
|
||||
"/storage/images/problem1.jpg",
|
||||
"/data/cache/problem2.png",
|
||||
"relative/path/problem3.jpeg"
|
||||
)
|
||||
listOf(
|
||||
"/storage/images/problem1.jpg",
|
||||
"/data/cache/problem2.png",
|
||||
"relative/path/problem3.jpeg",
|
||||
)
|
||||
|
||||
val relativePaths = convertToRelativePaths(originalPaths)
|
||||
|
||||
@@ -295,76 +295,76 @@ class BusinessLogicTests {
|
||||
|
||||
private fun createTestGym(): Gym {
|
||||
return Gym(
|
||||
id = "test_gym_1",
|
||||
name = "Test Climbing Gym",
|
||||
location = "Test City",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER, ClimbType.ROPE),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE, DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = "Test gym for unit testing",
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp()
|
||||
id = "test_gym_1",
|
||||
name = "Test Climbing Gym",
|
||||
location = "Test City",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER, ClimbType.ROPE),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE, DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = "Test gym for unit testing",
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTestProblem(
|
||||
gymId: String,
|
||||
climbType: ClimbType = ClimbType.BOULDER
|
||||
gymId: String,
|
||||
climbType: ClimbType = ClimbType.BOULDER,
|
||||
): Problem {
|
||||
val difficulty =
|
||||
when (climbType) {
|
||||
ClimbType.BOULDER -> DifficultyGrade(DifficultySystem.V_SCALE, "V5")
|
||||
ClimbType.ROPE -> DifficultyGrade(DifficultySystem.YDS, "5.10a")
|
||||
}
|
||||
when (climbType) {
|
||||
ClimbType.BOULDER -> DifficultyGrade(DifficultySystem.V_SCALE, "V5")
|
||||
ClimbType.ROPE -> DifficultyGrade(DifficultySystem.YDS, "5.10a")
|
||||
}
|
||||
|
||||
return Problem(
|
||||
id = "test_problem_${java.util.UUID.randomUUID()}",
|
||||
gymId = gymId,
|
||||
name = "Test Problem",
|
||||
description = "A test climbing problem",
|
||||
climbType = climbType,
|
||||
difficulty = difficulty,
|
||||
tags = listOf("test", "overhang"),
|
||||
location = "Wall A",
|
||||
imagePaths = emptyList(),
|
||||
isActive = true,
|
||||
dateSet = "2024-01-01",
|
||||
notes = null,
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp()
|
||||
id = "test_problem_${java.util.UUID.randomUUID()}",
|
||||
gymId = gymId,
|
||||
name = "Test Problem",
|
||||
description = "A test climbing problem",
|
||||
climbType = climbType,
|
||||
difficulty = difficulty,
|
||||
tags = listOf("test", "overhang"),
|
||||
location = "Wall A",
|
||||
imagePaths = emptyList(),
|
||||
isActive = true,
|
||||
dateSet = "2024-01-01",
|
||||
notes = null,
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTestProblemWithGrade(gymId: String, grade: String): Problem {
|
||||
return Problem(
|
||||
id = "test_problem_${java.util.UUID.randomUUID()}",
|
||||
gymId = gymId,
|
||||
name = "Test Problem $grade",
|
||||
description = null,
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, grade),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = emptyList(),
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp()
|
||||
id = "test_problem_${java.util.UUID.randomUUID()}",
|
||||
gymId = gymId,
|
||||
name = "Test Problem $grade",
|
||||
description = null,
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, grade),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = emptyList(),
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = getCurrentTimestamp(),
|
||||
updatedAt = getCurrentTimestamp(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createAttemptWithTimestamp(
|
||||
sessionId: String,
|
||||
problemId: String,
|
||||
timestamp: String,
|
||||
result: AttemptResult
|
||||
sessionId: String,
|
||||
problemId: String,
|
||||
timestamp: String,
|
||||
result: AttemptResult,
|
||||
): Attempt {
|
||||
return Attempt.create(
|
||||
sessionId = sessionId,
|
||||
problemId = problemId,
|
||||
result = result,
|
||||
timestamp = timestamp
|
||||
sessionId = sessionId,
|
||||
problemId = problemId,
|
||||
result = result,
|
||||
timestamp = timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -373,126 +373,126 @@ class BusinessLogicTests {
|
||||
}
|
||||
|
||||
private fun calculateSessionStatistics(
|
||||
session: ClimbSession,
|
||||
attempts: List<Attempt>
|
||||
session: ClimbSession,
|
||||
attempts: List<Attempt>,
|
||||
): SessionStatistics {
|
||||
val successful =
|
||||
attempts.count {
|
||||
it.result == AttemptResult.SUCCESS || it.result == AttemptResult.FLASH
|
||||
}
|
||||
attempts.count {
|
||||
it.result == AttemptResult.SUCCESS || it.result == AttemptResult.FLASH
|
||||
}
|
||||
val uniqueProblems = attempts.map { it.problemId }.toSet().size
|
||||
val successRate = (successful.toDouble() / attempts.size) * 100
|
||||
|
||||
return SessionStatistics(
|
||||
totalAttempts = attempts.size,
|
||||
successfulAttempts = successful,
|
||||
uniqueProblems = uniqueProblems,
|
||||
successRate = successRate
|
||||
totalAttempts = attempts.size,
|
||||
successfulAttempts = successful,
|
||||
uniqueProblems = uniqueProblems,
|
||||
successRate = successRate,
|
||||
)
|
||||
}
|
||||
|
||||
private fun calculateDifficultyProgression(
|
||||
attempts: List<Attempt>,
|
||||
problems: List<Problem>
|
||||
attempts: List<Attempt>,
|
||||
problems: List<Problem>,
|
||||
): DifficultyProgression {
|
||||
val problemMap = problems.associateBy { it.id }
|
||||
val grades =
|
||||
attempts
|
||||
.mapNotNull { attempt -> problemMap[attempt.problemId]?.difficulty?.grade }
|
||||
.filter { it.startsWith("V") }
|
||||
attempts
|
||||
.mapNotNull { attempt -> problemMap[attempt.problemId]?.difficulty?.grade }
|
||||
.filter { it.startsWith("V") }
|
||||
|
||||
val numericGrades =
|
||||
grades.mapNotNull { grade ->
|
||||
when (grade) {
|
||||
"VB" -> 0
|
||||
else -> grade.removePrefix("V").toIntOrNull()
|
||||
}
|
||||
grades.mapNotNull { grade ->
|
||||
when (grade) {
|
||||
"VB" -> 0
|
||||
else -> grade.removePrefix("V").toIntOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
val minGrade = "V${numericGrades.minOrNull() ?: 0}".replace("V0", "VB")
|
||||
val maxGrade = "V${numericGrades.maxOrNull() ?: 0}".replace("V0", "VB")
|
||||
val avgGrade = numericGrades.average()
|
||||
val showsProgression =
|
||||
numericGrades.size > 1 &&
|
||||
(numericGrades.maxOrNull() ?: 0) > (numericGrades.minOrNull() ?: 0)
|
||||
numericGrades.size > 1 &&
|
||||
(numericGrades.maxOrNull() ?: 0) > (numericGrades.minOrNull() ?: 0)
|
||||
|
||||
return DifficultyProgression(minGrade, maxGrade, avgGrade, showsProgression)
|
||||
}
|
||||
|
||||
private fun createBackupData(
|
||||
gyms: List<Gym>,
|
||||
problems: List<Problem>,
|
||||
sessions: List<ClimbSession>,
|
||||
attempts: List<Attempt>
|
||||
gyms: List<Gym>,
|
||||
problems: List<Problem>,
|
||||
sessions: List<ClimbSession>,
|
||||
attempts: List<Attempt>,
|
||||
): ClimbDataBackup {
|
||||
return ClimbDataBackup(
|
||||
exportedAt = getCurrentTimestamp(),
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms =
|
||||
gyms.map { gym ->
|
||||
BackupGym(
|
||||
id = gym.id,
|
||||
name = gym.name,
|
||||
location = gym.location,
|
||||
supportedClimbTypes = gym.supportedClimbTypes,
|
||||
difficultySystems = gym.difficultySystems,
|
||||
customDifficultyGrades = gym.customDifficultyGrades,
|
||||
notes = gym.notes,
|
||||
createdAt = gym.createdAt,
|
||||
updatedAt = gym.updatedAt
|
||||
)
|
||||
},
|
||||
problems =
|
||||
problems.map { problem ->
|
||||
BackupProblem(
|
||||
id = problem.id,
|
||||
gymId = problem.gymId,
|
||||
name = problem.name,
|
||||
description = problem.description,
|
||||
climbType = problem.climbType,
|
||||
difficulty = problem.difficulty,
|
||||
tags = problem.tags,
|
||||
location = problem.location,
|
||||
imagePaths = problem.imagePaths,
|
||||
isActive = problem.isActive,
|
||||
dateSet = problem.dateSet,
|
||||
notes = problem.notes,
|
||||
createdAt = problem.createdAt,
|
||||
updatedAt = problem.updatedAt
|
||||
)
|
||||
},
|
||||
sessions =
|
||||
sessions.map { session ->
|
||||
BackupClimbSession(
|
||||
id = session.id,
|
||||
gymId = session.gymId,
|
||||
date = session.date,
|
||||
startTime = session.startTime,
|
||||
endTime = session.endTime,
|
||||
duration = session.duration,
|
||||
status = session.status,
|
||||
notes = session.notes,
|
||||
createdAt = session.createdAt,
|
||||
updatedAt = session.updatedAt
|
||||
)
|
||||
},
|
||||
attempts =
|
||||
attempts.map { attempt ->
|
||||
BackupAttempt(
|
||||
id = attempt.id,
|
||||
sessionId = attempt.sessionId,
|
||||
problemId = attempt.problemId,
|
||||
result = attempt.result,
|
||||
highestHold = attempt.highestHold,
|
||||
notes = attempt.notes,
|
||||
duration = attempt.duration,
|
||||
restTime = attempt.restTime,
|
||||
timestamp = attempt.timestamp,
|
||||
createdAt = attempt.createdAt,
|
||||
updatedAt = attempt.updatedAt,
|
||||
)
|
||||
}
|
||||
exportedAt = getCurrentTimestamp(),
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms =
|
||||
gyms.map { gym ->
|
||||
BackupGym(
|
||||
id = gym.id,
|
||||
name = gym.name,
|
||||
location = gym.location,
|
||||
supportedClimbTypes = gym.supportedClimbTypes,
|
||||
difficultySystems = gym.difficultySystems,
|
||||
customDifficultyGrades = gym.customDifficultyGrades,
|
||||
notes = gym.notes,
|
||||
createdAt = gym.createdAt,
|
||||
updatedAt = gym.updatedAt,
|
||||
)
|
||||
},
|
||||
problems =
|
||||
problems.map { problem ->
|
||||
BackupProblem(
|
||||
id = problem.id,
|
||||
gymId = problem.gymId,
|
||||
name = problem.name,
|
||||
description = problem.description,
|
||||
climbType = problem.climbType,
|
||||
difficulty = problem.difficulty,
|
||||
tags = problem.tags,
|
||||
location = problem.location,
|
||||
imagePaths = problem.imagePaths,
|
||||
isActive = problem.isActive,
|
||||
dateSet = problem.dateSet,
|
||||
notes = problem.notes,
|
||||
createdAt = problem.createdAt,
|
||||
updatedAt = problem.updatedAt,
|
||||
)
|
||||
},
|
||||
sessions =
|
||||
sessions.map { session ->
|
||||
BackupClimbSession(
|
||||
id = session.id,
|
||||
gymId = session.gymId,
|
||||
date = session.date,
|
||||
startTime = session.startTime,
|
||||
endTime = session.endTime,
|
||||
duration = session.duration,
|
||||
status = session.status,
|
||||
notes = session.notes,
|
||||
createdAt = session.createdAt,
|
||||
updatedAt = session.updatedAt,
|
||||
)
|
||||
},
|
||||
attempts =
|
||||
attempts.map { attempt ->
|
||||
BackupAttempt(
|
||||
id = attempt.id,
|
||||
sessionId = attempt.sessionId,
|
||||
problemId = attempt.problemId,
|
||||
result = attempt.result,
|
||||
highestHold = attempt.highestHold,
|
||||
notes = attempt.notes,
|
||||
duration = attempt.duration,
|
||||
restTime = attempt.restTime,
|
||||
timestamp = attempt.timestamp,
|
||||
createdAt = attempt.createdAt,
|
||||
updatedAt = attempt.updatedAt,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -501,8 +501,8 @@ class BusinessLogicTests {
|
||||
val gymIds = backup.gyms.map { it.id }.toSet()
|
||||
backup.problems.forEach { problem ->
|
||||
assertTrue(
|
||||
"Problem ${problem.id} references non-existent gym ${problem.gymId}",
|
||||
gymIds.contains(problem.gymId)
|
||||
"Problem ${problem.id} references non-existent gym ${problem.gymId}",
|
||||
gymIds.contains(problem.gymId),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -510,8 +510,8 @@ class BusinessLogicTests {
|
||||
val sessionIds = backup.sessions.map { it.id }.toSet()
|
||||
backup.attempts.forEach { attempt ->
|
||||
assertTrue(
|
||||
"Attempt ${attempt.id} references non-existent session ${attempt.sessionId}",
|
||||
sessionIds.contains(attempt.sessionId)
|
||||
"Attempt ${attempt.id} references non-existent session ${attempt.sessionId}",
|
||||
sessionIds.contains(attempt.sessionId),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -519,19 +519,19 @@ class BusinessLogicTests {
|
||||
val problemIds = backup.problems.map { it.id }.toSet()
|
||||
backup.attempts.forEach { attempt ->
|
||||
assertTrue(
|
||||
"Attempt ${attempt.id} references non-existent problem ${attempt.problemId}",
|
||||
problemIds.contains(attempt.problemId)
|
||||
"Attempt ${attempt.id} references non-existent problem ${attempt.problemId}",
|
||||
problemIds.contains(attempt.problemId),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCompatibleClimbType(
|
||||
gym: Gym,
|
||||
climbType: ClimbType,
|
||||
difficultySystem: DifficultySystem
|
||||
gym: Gym,
|
||||
climbType: ClimbType,
|
||||
difficultySystem: DifficultySystem,
|
||||
): Boolean {
|
||||
return gym.supportedClimbTypes.contains(climbType) &&
|
||||
gym.difficultySystems.contains(difficultySystem)
|
||||
gym.difficultySystems.contains(difficultySystem)
|
||||
}
|
||||
|
||||
private fun calculateSessionDuration(startTime: String, endTime: String): Long {
|
||||
@@ -541,19 +541,19 @@ class BusinessLogicTests {
|
||||
}
|
||||
|
||||
private fun isValidGradeCombination(
|
||||
climbType: ClimbType,
|
||||
difficultySystem: DifficultySystem
|
||||
climbType: ClimbType,
|
||||
difficultySystem: DifficultySystem,
|
||||
): Boolean {
|
||||
return when (climbType) {
|
||||
ClimbType.BOULDER ->
|
||||
difficultySystem in
|
||||
listOf(
|
||||
DifficultySystem.V_SCALE,
|
||||
DifficultySystem.FONT,
|
||||
DifficultySystem.CUSTOM
|
||||
)
|
||||
difficultySystem in
|
||||
listOf(
|
||||
DifficultySystem.V_SCALE,
|
||||
DifficultySystem.FONT,
|
||||
DifficultySystem.CUSTOM,
|
||||
)
|
||||
ClimbType.ROPE ->
|
||||
difficultySystem in listOf(DifficultySystem.YDS, DifficultySystem.CUSTOM)
|
||||
difficultySystem in listOf(DifficultySystem.YDS, DifficultySystem.CUSTOM)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,29 +568,29 @@ class BusinessLogicTests {
|
||||
// Data classes for testing
|
||||
|
||||
data class SessionStatistics(
|
||||
val totalAttempts: Int,
|
||||
val successfulAttempts: Int,
|
||||
val uniqueProblems: Int,
|
||||
val successRate: Double
|
||||
val totalAttempts: Int,
|
||||
val successfulAttempts: Int,
|
||||
val uniqueProblems: Int,
|
||||
val successRate: Double,
|
||||
)
|
||||
|
||||
data class DifficultyProgression(
|
||||
val minGrade: String,
|
||||
val maxGrade: String,
|
||||
val averageGrade: Double,
|
||||
val showsProgression: Boolean
|
||||
val minGrade: String,
|
||||
val maxGrade: String,
|
||||
val averageGrade: Double,
|
||||
val showsProgression: Boolean,
|
||||
)
|
||||
|
||||
data class AttemptSequence(val attempts: List<Attempt>) {
|
||||
val totalAttempts = attempts.size
|
||||
val failedAttempts =
|
||||
attempts.count {
|
||||
it.result == AttemptResult.FALL || it.result == AttemptResult.NO_PROGRESS
|
||||
}
|
||||
attempts.count {
|
||||
it.result == AttemptResult.FALL || it.result == AttemptResult.NO_PROGRESS
|
||||
}
|
||||
val successfulAttempts =
|
||||
attempts.count {
|
||||
it.result == AttemptResult.SUCCESS || it.result == AttemptResult.FLASH
|
||||
}
|
||||
attempts.count {
|
||||
it.result == AttemptResult.SUCCESS || it.result == AttemptResult.FLASH
|
||||
}
|
||||
val finalResult = attempts.lastOrNull()?.result
|
||||
|
||||
fun isValidSequence(): Boolean {
|
||||
|
||||
@@ -2,10 +2,10 @@ package com.atridad.ascently
|
||||
|
||||
import com.atridad.ascently.data.format.*
|
||||
import com.atridad.ascently.data.model.*
|
||||
import java.time.Instant
|
||||
import java.time.format.DateTimeFormatter
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import java.time.Instant
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class DataModelTests {
|
||||
|
||||
@@ -141,17 +141,17 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testBackupGymCreationAndValidation() {
|
||||
val gym =
|
||||
BackupGym(
|
||||
id = "gym123",
|
||||
name = "Test Climbing Gym",
|
||||
location = "Test City",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER, ClimbType.ROPE),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE, DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = "Great gym for beginners",
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z"
|
||||
)
|
||||
BackupGym(
|
||||
id = "gym123",
|
||||
name = "Test Climbing Gym",
|
||||
location = "Test City",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER, ClimbType.ROPE),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE, DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = "Great gym for beginners",
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z",
|
||||
)
|
||||
|
||||
assertEquals("gym123", gym.id)
|
||||
assertEquals("Test Climbing Gym", gym.name)
|
||||
@@ -167,22 +167,22 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testBackupProblemCreationAndValidation() {
|
||||
val problem =
|
||||
BackupProblem(
|
||||
id = "problem123",
|
||||
gymId = "gym123",
|
||||
name = "Test Problem",
|
||||
description = "A challenging boulder problem",
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V5"),
|
||||
tags = listOf("overhang", "crimpy"),
|
||||
location = "Wall A",
|
||||
imagePaths = listOf("image1.jpg", "image2.jpg"),
|
||||
isActive = true,
|
||||
dateSet = "2024-01-01",
|
||||
notes = "Watch the start holds",
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z"
|
||||
)
|
||||
BackupProblem(
|
||||
id = "problem123",
|
||||
gymId = "gym123",
|
||||
name = "Test Problem",
|
||||
description = "A challenging boulder problem",
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V5"),
|
||||
tags = listOf("overhang", "crimpy"),
|
||||
location = "Wall A",
|
||||
imagePaths = listOf("image1.jpg", "image2.jpg"),
|
||||
isActive = true,
|
||||
dateSet = "2024-01-01",
|
||||
notes = "Watch the start holds",
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z",
|
||||
)
|
||||
|
||||
assertEquals("problem123", problem.id)
|
||||
assertEquals("gym123", problem.gymId)
|
||||
@@ -190,25 +190,25 @@ class DataModelTests {
|
||||
assertEquals(ClimbType.BOULDER, problem.climbType)
|
||||
assertEquals("V5", problem.difficulty.grade)
|
||||
assertTrue(problem.isActive)
|
||||
assertEquals(2, problem.tags.size)
|
||||
assertEquals(2, problem.tags?.size ?: 0)
|
||||
assertEquals(2, problem.imagePaths?.size ?: 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBackupClimbSessionCreationAndValidation() {
|
||||
val session =
|
||||
BackupClimbSession(
|
||||
id = "session123",
|
||||
gymId = "gym123",
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00Z",
|
||||
endTime = "2024-01-01T12:00:00Z",
|
||||
duration = 7200,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = "Great session today",
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T12:00:00Z"
|
||||
)
|
||||
BackupClimbSession(
|
||||
id = "session123",
|
||||
gymId = "gym123",
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00Z",
|
||||
endTime = "2024-01-01T12:00:00Z",
|
||||
duration = 7200,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = "Great session today",
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T12:00:00Z",
|
||||
)
|
||||
|
||||
assertEquals("session123", session.id)
|
||||
assertEquals("gym123", session.gymId)
|
||||
@@ -220,19 +220,19 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testBackupAttemptCreationAndValidation() {
|
||||
val attempt =
|
||||
BackupAttempt(
|
||||
id = "attempt123",
|
||||
sessionId = "session123",
|
||||
problemId = "problem123",
|
||||
result = AttemptResult.SUCCESS,
|
||||
highestHold = "Top",
|
||||
notes = "Stuck it on second try",
|
||||
duration = 300,
|
||||
restTime = 120,
|
||||
timestamp = "2024-01-01T10:30:00Z",
|
||||
createdAt = "2024-01-01T10:30:00Z",
|
||||
updatedAt = "2024-01-01T10:30:00Z"
|
||||
)
|
||||
BackupAttempt(
|
||||
id = "attempt123",
|
||||
sessionId = "session123",
|
||||
problemId = "problem123",
|
||||
result = AttemptResult.SUCCESS,
|
||||
highestHold = "Top",
|
||||
notes = "Stuck it on second try",
|
||||
duration = 300,
|
||||
restTime = 120,
|
||||
timestamp = "2024-01-01T10:30:00Z",
|
||||
createdAt = "2024-01-01T10:30:00Z",
|
||||
updatedAt = "2024-01-01T10:30:00Z",
|
||||
)
|
||||
|
||||
assertEquals("attempt123", attempt.id)
|
||||
assertEquals("session123", attempt.sessionId)
|
||||
@@ -246,15 +246,15 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testClimbDataBackupCreationAndValidation() {
|
||||
val backup =
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T10:00:00Z",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = emptyList(),
|
||||
problems = emptyList(),
|
||||
sessions = emptyList(),
|
||||
attempts = emptyList()
|
||||
)
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T10:00:00Z",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = emptyList(),
|
||||
problems = emptyList(),
|
||||
sessions = emptyList(),
|
||||
attempts = emptyList(),
|
||||
)
|
||||
|
||||
assertEquals("2.0", backup.version)
|
||||
assertEquals("2.0", backup.formatVersion)
|
||||
@@ -280,18 +280,18 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testSessionDurationCalculation() {
|
||||
val session =
|
||||
BackupClimbSession(
|
||||
id = "test",
|
||||
gymId = "gym1",
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00Z",
|
||||
endTime = "2024-01-01T12:00:00Z",
|
||||
duration = 7200,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T12:00:00Z"
|
||||
)
|
||||
BackupClimbSession(
|
||||
id = "test",
|
||||
gymId = "gym1",
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00Z",
|
||||
endTime = "2024-01-01T12:00:00Z",
|
||||
duration = 7200,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T12:00:00Z",
|
||||
)
|
||||
|
||||
assertEquals(7200L, session.duration)
|
||||
val hours = session.duration!! / 3600
|
||||
@@ -301,21 +301,21 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testEmptyCollectionsHandling() {
|
||||
val gym =
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Test Gym",
|
||||
location = null,
|
||||
supportedClimbTypes = emptyList(),
|
||||
difficultySystems = emptyList(),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z"
|
||||
)
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Test Gym",
|
||||
location = null,
|
||||
supportedClimbTypes = emptyList(),
|
||||
difficultySystems = emptyList(),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z",
|
||||
)
|
||||
|
||||
assertTrue(gym.supportedClimbTypes.isEmpty())
|
||||
assertTrue(gym.difficultySystems.isEmpty())
|
||||
assertTrue(gym.customDifficultyGrades.isEmpty())
|
||||
assertTrue(gym.supportedClimbTypes?.isEmpty() ?: true)
|
||||
assertTrue(gym.difficultySystems?.isEmpty() ?: true)
|
||||
assertTrue(gym.customDifficultyGrades?.isEmpty() ?: true)
|
||||
assertNull(gym.location)
|
||||
assertNull(gym.notes)
|
||||
}
|
||||
@@ -323,29 +323,29 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testNullableFieldsHandling() {
|
||||
val problem =
|
||||
BackupProblem(
|
||||
id = "problem1",
|
||||
gymId = "gym1",
|
||||
name = null,
|
||||
description = null,
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V1"),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z"
|
||||
)
|
||||
BackupProblem(
|
||||
id = "problem1",
|
||||
gymId = "gym1",
|
||||
name = null,
|
||||
description = null,
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V1"),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z",
|
||||
)
|
||||
|
||||
assertNull(problem.name)
|
||||
assertNull(problem.description)
|
||||
assertNull(problem.location)
|
||||
assertNull(problem.dateSet)
|
||||
assertNull(problem.notes)
|
||||
assertTrue(problem.tags.isEmpty())
|
||||
assertTrue(problem.tags?.isEmpty() ?: true)
|
||||
assertNull(problem.imagePaths)
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testBackupDataFormatValidation() {
|
||||
val testJson =
|
||||
"""
|
||||
"""
|
||||
{
|
||||
"exportedAt": "2024-01-01T10:00:00Z",
|
||||
"version": "2.0",
|
||||
@@ -372,7 +372,7 @@ class DataModelTests {
|
||||
"sessions": [],
|
||||
"attempts": []
|
||||
}
|
||||
""".trimIndent()
|
||||
""".trimIndent()
|
||||
|
||||
assertTrue(testJson.contains("exportedAt"))
|
||||
assertTrue(testJson.contains("version"))
|
||||
@@ -397,44 +397,44 @@ class DataModelTests {
|
||||
fun testClimbTypeAndDifficultySystemCompatibility() {
|
||||
// Test that V_SCALE works with BOULDER
|
||||
val boulderProblem =
|
||||
BackupProblem(
|
||||
id = "boulder1",
|
||||
gymId = "gym1",
|
||||
name = "Boulder Problem",
|
||||
description = null,
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V3"),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z"
|
||||
)
|
||||
BackupProblem(
|
||||
id = "boulder1",
|
||||
gymId = "gym1",
|
||||
name = "Boulder Problem",
|
||||
description = null,
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V3"),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z",
|
||||
)
|
||||
|
||||
assertEquals(ClimbType.BOULDER, boulderProblem.climbType)
|
||||
assertEquals(DifficultySystem.V_SCALE, boulderProblem.difficulty.system)
|
||||
|
||||
// Test that YDS works with ROPE
|
||||
val ropeProblem =
|
||||
BackupProblem(
|
||||
id = "rope1",
|
||||
gymId = "gym1",
|
||||
name = "Rope Problem",
|
||||
description = null,
|
||||
climbType = ClimbType.ROPE,
|
||||
difficulty = DifficultyGrade(DifficultySystem.YDS, "5.10a"),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z"
|
||||
)
|
||||
BackupProblem(
|
||||
id = "rope1",
|
||||
gymId = "gym1",
|
||||
name = "Rope Problem",
|
||||
description = null,
|
||||
climbType = ClimbType.ROPE,
|
||||
difficulty = DifficultyGrade(DifficultySystem.YDS, "5.10a"),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z",
|
||||
)
|
||||
|
||||
assertEquals(ClimbType.ROPE, ropeProblem.climbType)
|
||||
assertEquals(DifficultySystem.YDS, ropeProblem.difficulty.system)
|
||||
@@ -473,12 +473,12 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testAttemptResultValidation() {
|
||||
val validResults =
|
||||
listOf(
|
||||
AttemptResult.SUCCESS,
|
||||
AttemptResult.FALL,
|
||||
AttemptResult.NO_PROGRESS,
|
||||
AttemptResult.FLASH
|
||||
)
|
||||
listOf(
|
||||
AttemptResult.SUCCESS,
|
||||
AttemptResult.FALL,
|
||||
AttemptResult.NO_PROGRESS,
|
||||
AttemptResult.FLASH,
|
||||
)
|
||||
|
||||
assertEquals(4, validResults.size)
|
||||
assertTrue(validResults.contains(AttemptResult.SUCCESS))
|
||||
@@ -490,7 +490,7 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testSessionStatusValidation() {
|
||||
val validStatuses =
|
||||
listOf(SessionStatus.ACTIVE, SessionStatus.COMPLETED, SessionStatus.PAUSED)
|
||||
listOf(SessionStatus.ACTIVE, SessionStatus.COMPLETED, SessionStatus.PAUSED)
|
||||
|
||||
assertEquals(3, validStatuses.size)
|
||||
assertTrue(validStatuses.contains(SessionStatus.ACTIVE))
|
||||
@@ -501,64 +501,64 @@ class DataModelTests {
|
||||
@Test
|
||||
fun testClimbDataIntegrity() {
|
||||
val gym =
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Test Gym",
|
||||
location = "Test City",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z"
|
||||
)
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Test Gym",
|
||||
location = "Test City",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z",
|
||||
)
|
||||
|
||||
val problem =
|
||||
BackupProblem(
|
||||
id = "problem1",
|
||||
gymId = gym.id,
|
||||
name = "Test Problem",
|
||||
description = null,
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V3"),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z"
|
||||
)
|
||||
BackupProblem(
|
||||
id = "problem1",
|
||||
gymId = gym.id,
|
||||
name = "Test Problem",
|
||||
description = null,
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V3"),
|
||||
tags = emptyList(),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T10:00:00Z",
|
||||
)
|
||||
|
||||
val session =
|
||||
BackupClimbSession(
|
||||
id = "session1",
|
||||
gymId = gym.id,
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00Z",
|
||||
endTime = "2024-01-01T11:00:00Z",
|
||||
duration = 3600,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T11:00:00Z"
|
||||
)
|
||||
BackupClimbSession(
|
||||
id = "session1",
|
||||
gymId = gym.id,
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00Z",
|
||||
endTime = "2024-01-01T11:00:00Z",
|
||||
duration = 3600,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00Z",
|
||||
updatedAt = "2024-01-01T11:00:00Z",
|
||||
)
|
||||
|
||||
val attempt =
|
||||
BackupAttempt(
|
||||
id = "attempt1",
|
||||
sessionId = session.id,
|
||||
problemId = problem.id,
|
||||
result = AttemptResult.SUCCESS,
|
||||
highestHold = null,
|
||||
notes = null,
|
||||
duration = 120,
|
||||
restTime = null,
|
||||
timestamp = "2024-01-01T10:30:00Z",
|
||||
createdAt = "2024-01-01T10:30:00Z",
|
||||
updatedAt = "2024-01-01T10:30:00Z"
|
||||
)
|
||||
BackupAttempt(
|
||||
id = "attempt1",
|
||||
sessionId = session.id,
|
||||
problemId = problem.id,
|
||||
result = AttemptResult.SUCCESS,
|
||||
highestHold = null,
|
||||
notes = null,
|
||||
duration = 120,
|
||||
restTime = null,
|
||||
timestamp = "2024-01-01T10:30:00Z",
|
||||
createdAt = "2024-01-01T10:30:00Z",
|
||||
updatedAt = "2024-01-01T10:30:00Z",
|
||||
)
|
||||
|
||||
// Verify referential integrity
|
||||
assertEquals(gym.id, problem.gymId)
|
||||
|
||||
@@ -11,197 +11,197 @@ class SyncMergeLogicTest {
|
||||
fun `test intelligent merge preserves all data`() {
|
||||
// Create local data
|
||||
val localGyms =
|
||||
listOf(
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Local Gym 1",
|
||||
location = "Local Location",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00"
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Local Gym 1",
|
||||
location = "Local Location",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER),
|
||||
difficultySystems = listOf(DifficultySystem.V_SCALE),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00",
|
||||
),
|
||||
)
|
||||
|
||||
val localProblems =
|
||||
listOf(
|
||||
BackupProblem(
|
||||
id = "problem1",
|
||||
gymId = "gym1",
|
||||
name = "Local Problem",
|
||||
description = "Local description",
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V5"),
|
||||
tags = listOf("local"),
|
||||
location = null,
|
||||
imagePaths = listOf("local_image.jpg"),
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00"
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
BackupProblem(
|
||||
id = "problem1",
|
||||
gymId = "gym1",
|
||||
name = "Local Problem",
|
||||
description = "Local description",
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V5"),
|
||||
tags = listOf("local"),
|
||||
location = null,
|
||||
imagePaths = listOf("local_image.jpg"),
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00",
|
||||
),
|
||||
)
|
||||
|
||||
val localSessions =
|
||||
listOf(
|
||||
BackupClimbSession(
|
||||
id = "session1",
|
||||
gymId = "gym1",
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00",
|
||||
endTime = "2024-01-01T12:00:00",
|
||||
duration = 7200,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00"
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
BackupClimbSession(
|
||||
id = "session1",
|
||||
gymId = "gym1",
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00",
|
||||
endTime = "2024-01-01T12:00:00",
|
||||
duration = 7200,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00",
|
||||
),
|
||||
)
|
||||
|
||||
val localAttempts =
|
||||
listOf(
|
||||
BackupAttempt(
|
||||
id = "attempt1",
|
||||
sessionId = "session1",
|
||||
problemId = "problem1",
|
||||
result = AttemptResult.SUCCESS,
|
||||
highestHold = null,
|
||||
notes = null,
|
||||
duration = 300,
|
||||
restTime = null,
|
||||
timestamp = "2024-01-01T10:30:00",
|
||||
createdAt = "2024-01-01T10:30:00",
|
||||
updatedAt = "2024-01-01T10:30:00"
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
BackupAttempt(
|
||||
id = "attempt1",
|
||||
sessionId = "session1",
|
||||
problemId = "problem1",
|
||||
result = AttemptResult.SUCCESS,
|
||||
highestHold = null,
|
||||
notes = null,
|
||||
duration = 300,
|
||||
restTime = null,
|
||||
timestamp = "2024-01-01T10:30:00",
|
||||
createdAt = "2024-01-01T10:30:00",
|
||||
updatedAt = "2024-01-01T10:30:00",
|
||||
),
|
||||
)
|
||||
|
||||
val localBackup =
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T10:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = localGyms,
|
||||
problems = localProblems,
|
||||
sessions = localSessions,
|
||||
attempts = localAttempts
|
||||
)
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T10:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = localGyms,
|
||||
problems = localProblems,
|
||||
sessions = localSessions,
|
||||
attempts = localAttempts,
|
||||
)
|
||||
|
||||
// Create server data with some overlapping and some unique data
|
||||
val serverGyms =
|
||||
listOf(
|
||||
// Same gym but with newer update
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Updated Gym 1",
|
||||
location = "Updated Location",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER, ClimbType.ROPE),
|
||||
difficultySystems =
|
||||
listOf(DifficultySystem.V_SCALE, DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = "Updated notes",
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T12:00:00" // Newer update
|
||||
),
|
||||
// Unique server gym
|
||||
BackupGym(
|
||||
id = "gym2",
|
||||
name = "Server Gym 2",
|
||||
location = "Server Location",
|
||||
supportedClimbTypes = listOf(ClimbType.ROPE),
|
||||
difficultySystems = listOf(DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T11:00:00",
|
||||
updatedAt = "2024-01-01T11:00:00"
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
// Same gym but with newer update
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Updated Gym 1",
|
||||
location = "Updated Location",
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER, ClimbType.ROPE),
|
||||
difficultySystems =
|
||||
listOf(DifficultySystem.V_SCALE, DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = "Updated notes",
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T12:00:00", // Newer update
|
||||
),
|
||||
// Unique server gym
|
||||
BackupGym(
|
||||
id = "gym2",
|
||||
name = "Server Gym 2",
|
||||
location = "Server Location",
|
||||
supportedClimbTypes = listOf(ClimbType.ROPE),
|
||||
difficultySystems = listOf(DifficultySystem.YDS),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T11:00:00",
|
||||
updatedAt = "2024-01-01T11:00:00",
|
||||
),
|
||||
)
|
||||
|
||||
val serverProblems =
|
||||
listOf(
|
||||
// Same problem but with newer update and different images
|
||||
BackupProblem(
|
||||
id = "problem1",
|
||||
gymId = "gym1",
|
||||
name = "Updated Problem",
|
||||
description = "Updated description",
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V5"),
|
||||
tags = listOf("updated", "server"),
|
||||
location = "Updated location",
|
||||
imagePaths = listOf("server_image.jpg"),
|
||||
isActive = true,
|
||||
dateSet = "2024-01-01",
|
||||
notes = "Updated notes",
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T11:00:00" // Newer update
|
||||
),
|
||||
// Unique server problem
|
||||
BackupProblem(
|
||||
id = "problem2",
|
||||
gymId = "gym2",
|
||||
name = "Server Problem",
|
||||
description = "Server description",
|
||||
climbType = ClimbType.ROPE,
|
||||
difficulty = DifficultyGrade(DifficultySystem.YDS, "5.10a"),
|
||||
tags = listOf("server"),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T11:00:00",
|
||||
updatedAt = "2024-01-01T11:00:00"
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
// Same problem but with newer update and different images
|
||||
BackupProblem(
|
||||
id = "problem1",
|
||||
gymId = "gym1",
|
||||
name = "Updated Problem",
|
||||
description = "Updated description",
|
||||
climbType = ClimbType.BOULDER,
|
||||
difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V5"),
|
||||
tags = listOf("updated", "server"),
|
||||
location = "Updated location",
|
||||
imagePaths = listOf("server_image.jpg"),
|
||||
isActive = true,
|
||||
dateSet = "2024-01-01",
|
||||
notes = "Updated notes",
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T11:00:00", // Newer update
|
||||
),
|
||||
// Unique server problem
|
||||
BackupProblem(
|
||||
id = "problem2",
|
||||
gymId = "gym2",
|
||||
name = "Server Problem",
|
||||
description = "Server description",
|
||||
climbType = ClimbType.ROPE,
|
||||
difficulty = DifficultyGrade(DifficultySystem.YDS, "5.10a"),
|
||||
tags = listOf("server"),
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = true,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T11:00:00",
|
||||
updatedAt = "2024-01-01T11:00:00",
|
||||
),
|
||||
)
|
||||
|
||||
val serverSessions =
|
||||
listOf(
|
||||
// Unique server session
|
||||
BackupClimbSession(
|
||||
id = "session2",
|
||||
gymId = "gym2",
|
||||
date = "2024-01-02",
|
||||
startTime = "2024-01-02T14:00:00",
|
||||
endTime = "2024-01-02T16:00:00",
|
||||
duration = 7200,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = "Server session",
|
||||
createdAt = "2024-01-02T14:00:00",
|
||||
updatedAt = "2024-01-02T14:00:00"
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
// Unique server session
|
||||
BackupClimbSession(
|
||||
id = "session2",
|
||||
gymId = "gym2",
|
||||
date = "2024-01-02",
|
||||
startTime = "2024-01-02T14:00:00",
|
||||
endTime = "2024-01-02T16:00:00",
|
||||
duration = 7200,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = "Server session",
|
||||
createdAt = "2024-01-02T14:00:00",
|
||||
updatedAt = "2024-01-02T14:00:00",
|
||||
),
|
||||
)
|
||||
|
||||
val serverAttempts =
|
||||
listOf(
|
||||
// Unique server attempt
|
||||
BackupAttempt(
|
||||
id = "attempt2",
|
||||
sessionId = "session2",
|
||||
problemId = "problem2",
|
||||
result = AttemptResult.FALL,
|
||||
highestHold = "Last move",
|
||||
notes = "Almost had it",
|
||||
duration = 180,
|
||||
restTime = 60,
|
||||
timestamp = "2024-01-02T14:30:00",
|
||||
createdAt = "2024-01-02T14:30:00",
|
||||
updatedAt = "2024-01-02T14:30:00"
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
// Unique server attempt
|
||||
BackupAttempt(
|
||||
id = "attempt2",
|
||||
sessionId = "session2",
|
||||
problemId = "problem2",
|
||||
result = AttemptResult.FALL,
|
||||
highestHold = "Last move",
|
||||
notes = "Almost had it",
|
||||
duration = 180,
|
||||
restTime = 60,
|
||||
timestamp = "2024-01-02T14:30:00",
|
||||
createdAt = "2024-01-02T14:30:00",
|
||||
updatedAt = "2024-01-02T14:30:00",
|
||||
),
|
||||
)
|
||||
|
||||
val serverBackup =
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T12:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = serverGyms,
|
||||
problems = serverProblems,
|
||||
sessions = serverSessions,
|
||||
attempts = serverAttempts
|
||||
)
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T12:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = serverGyms,
|
||||
problems = serverProblems,
|
||||
sessions = serverSessions,
|
||||
attempts = serverAttempts,
|
||||
)
|
||||
|
||||
// Simulate merge logic
|
||||
val mergedBackup = performIntelligentMerge(localBackup, serverBackup)
|
||||
@@ -231,12 +231,12 @@ class SyncMergeLogicTest {
|
||||
|
||||
// Images should be merged (both local and server images preserved)
|
||||
assertTrue(
|
||||
"Should contain local image",
|
||||
mergedProblem1.imagePaths!!.contains("local_image.jpg")
|
||||
"Should contain local image",
|
||||
mergedProblem1.imagePaths!!.contains("local_image.jpg"),
|
||||
)
|
||||
assertTrue(
|
||||
"Should contain server image",
|
||||
mergedProblem1.imagePaths!!.contains("server_image.jpg")
|
||||
"Should contain server image",
|
||||
mergedProblem1.imagePaths!!.contains("server_image.jpg"),
|
||||
)
|
||||
assertEquals("Should have 2 images total", 2, mergedProblem1.imagePaths!!.size)
|
||||
|
||||
@@ -246,78 +246,78 @@ class SyncMergeLogicTest {
|
||||
|
||||
// Verify all sessions are preserved
|
||||
assertTrue(
|
||||
"Should contain local session",
|
||||
mergedBackup.sessions.any { it.id == "session1" }
|
||||
"Should contain local session",
|
||||
mergedBackup.sessions.any { it.id == "session1" },
|
||||
)
|
||||
assertTrue(
|
||||
"Should contain server session",
|
||||
mergedBackup.sessions.any { it.id == "session2" }
|
||||
"Should contain server session",
|
||||
mergedBackup.sessions.any { it.id == "session2" },
|
||||
)
|
||||
|
||||
// Verify all attempts are preserved
|
||||
assertTrue(
|
||||
"Should contain local attempt",
|
||||
mergedBackup.attempts.any { it.id == "attempt1" }
|
||||
"Should contain local attempt",
|
||||
mergedBackup.attempts.any { it.id == "attempt1" },
|
||||
)
|
||||
assertTrue(
|
||||
"Should contain server attempt",
|
||||
mergedBackup.attempts.any { it.id == "attempt2" }
|
||||
"Should contain server attempt",
|
||||
mergedBackup.attempts.any { it.id == "attempt2" },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test date comparison logic`() {
|
||||
assertTrue(
|
||||
"ISO instant should be newer",
|
||||
isNewerThan("2024-01-01T12:00:00Z", "2024-01-01T10:00:00Z")
|
||||
"ISO instant should be newer",
|
||||
isNewerThan("2024-01-01T12:00:00Z", "2024-01-01T10:00:00Z"),
|
||||
)
|
||||
assertFalse(
|
||||
"ISO instant should be older",
|
||||
isNewerThan("2024-01-01T10:00:00Z", "2024-01-01T12:00:00Z")
|
||||
"ISO instant should be older",
|
||||
isNewerThan("2024-01-01T10:00:00Z", "2024-01-01T12:00:00Z"),
|
||||
)
|
||||
assertTrue(
|
||||
"String comparison should work as fallback",
|
||||
isNewerThan("2024-01-02T10:00:00", "2024-01-01T10:00:00")
|
||||
"String comparison should work as fallback",
|
||||
isNewerThan("2024-01-02T10:00:00", "2024-01-01T10:00:00"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test empty data scenarios`() {
|
||||
val emptyBackup =
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T10:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = emptyList(),
|
||||
problems = emptyList(),
|
||||
sessions = emptyList(),
|
||||
attempts = emptyList()
|
||||
)
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T10:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = emptyList(),
|
||||
problems = emptyList(),
|
||||
sessions = emptyList(),
|
||||
attempts = emptyList(),
|
||||
)
|
||||
|
||||
val dataBackup =
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T10:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms =
|
||||
listOf(
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Test Gym",
|
||||
location = null,
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER),
|
||||
difficultySystems =
|
||||
listOf(DifficultySystem.V_SCALE),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00"
|
||||
)
|
||||
),
|
||||
problems = emptyList(),
|
||||
sessions = emptyList(),
|
||||
attempts = emptyList()
|
||||
)
|
||||
ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T10:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms =
|
||||
listOf(
|
||||
BackupGym(
|
||||
id = "gym1",
|
||||
name = "Test Gym",
|
||||
location = null,
|
||||
supportedClimbTypes = listOf(ClimbType.BOULDER),
|
||||
difficultySystems =
|
||||
listOf(DifficultySystem.V_SCALE),
|
||||
customDifficultyGrades = emptyList(),
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00",
|
||||
),
|
||||
),
|
||||
problems = emptyList(),
|
||||
sessions = emptyList(),
|
||||
attempts = emptyList(),
|
||||
)
|
||||
|
||||
// Test merging empty with data
|
||||
val merged1 = performIntelligentMerge(emptyBackup, dataBackup)
|
||||
@@ -334,8 +334,8 @@ class SyncMergeLogicTest {
|
||||
|
||||
// Helper methods that simulate the merge logic from SyncService
|
||||
private fun performIntelligentMerge(
|
||||
local: ClimbDataBackup,
|
||||
server: ClimbDataBackup
|
||||
local: ClimbDataBackup,
|
||||
server: ClimbDataBackup,
|
||||
): ClimbDataBackup {
|
||||
val mergedGyms = mergeGyms(local.gyms, server.gyms)
|
||||
val mergedProblems = mergeProblems(local.problems, server.problems)
|
||||
@@ -343,13 +343,13 @@ class SyncMergeLogicTest {
|
||||
val mergedAttempts = mergeAttempts(local.attempts, server.attempts)
|
||||
|
||||
return ClimbDataBackup(
|
||||
exportedAt = "2024-01-01T12:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = mergedGyms,
|
||||
problems = mergedProblems,
|
||||
sessions = mergedSessions,
|
||||
attempts = mergedAttempts
|
||||
exportedAt = "2024-01-01T12:00:00",
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
gyms = mergedGyms,
|
||||
problems = mergedProblems,
|
||||
sessions = mergedSessions,
|
||||
attempts = mergedAttempts,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -371,8 +371,8 @@ class SyncMergeLogicTest {
|
||||
}
|
||||
|
||||
private fun mergeProblems(
|
||||
local: List<BackupProblem>,
|
||||
server: List<BackupProblem>
|
||||
local: List<BackupProblem>,
|
||||
server: List<BackupProblem>,
|
||||
): List<BackupProblem> {
|
||||
val merged = mutableMapOf<String, BackupProblem>()
|
||||
|
||||
@@ -390,7 +390,7 @@ class SyncMergeLogicTest {
|
||||
serverProblem.imagePaths?.let { allImagePaths.addAll(it) }
|
||||
|
||||
merged[serverProblem.id] =
|
||||
serverProblem.withUpdatedImagePaths(allImagePaths.toList())
|
||||
serverProblem.withUpdatedImagePaths(allImagePaths.toList())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,8 +398,8 @@ class SyncMergeLogicTest {
|
||||
}
|
||||
|
||||
private fun mergeSessions(
|
||||
local: List<BackupClimbSession>,
|
||||
server: List<BackupClimbSession>
|
||||
local: List<BackupClimbSession>,
|
||||
server: List<BackupClimbSession>,
|
||||
): List<BackupClimbSession> {
|
||||
val merged = mutableMapOf<String, BackupClimbSession>()
|
||||
|
||||
@@ -419,8 +419,8 @@ class SyncMergeLogicTest {
|
||||
}
|
||||
|
||||
private fun mergeAttempts(
|
||||
local: List<BackupAttempt>,
|
||||
server: List<BackupAttempt>
|
||||
local: List<BackupAttempt>,
|
||||
server: List<BackupAttempt>,
|
||||
): List<BackupAttempt> {
|
||||
val merged = mutableMapOf<String, BackupAttempt>()
|
||||
|
||||
@@ -431,10 +431,10 @@ class SyncMergeLogicTest {
|
||||
server.forEach { serverAttempt ->
|
||||
val localAttempt = merged[serverAttempt.id]
|
||||
if (localAttempt == null ||
|
||||
isNewerThan(
|
||||
serverAttempt.updatedAt ?: serverAttempt.createdAt,
|
||||
localAttempt.updatedAt ?: localAttempt.createdAt
|
||||
)
|
||||
isNewerThan(
|
||||
serverAttempt.updatedAt ?: serverAttempt.createdAt,
|
||||
localAttempt.updatedAt ?: localAttempt.createdAt,
|
||||
)
|
||||
) {
|
||||
merged[serverAttempt.id] = serverAttempt
|
||||
}
|
||||
@@ -458,32 +458,32 @@ class SyncMergeLogicTest {
|
||||
@Test
|
||||
fun `test active sessions excluded from sync`() {
|
||||
val allLocalSessions =
|
||||
listOf(
|
||||
BackupClimbSession(
|
||||
id = "active_session_1",
|
||||
gymId = "gym1",
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00",
|
||||
endTime = null,
|
||||
duration = null,
|
||||
status = SessionStatus.ACTIVE,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00"
|
||||
),
|
||||
BackupClimbSession(
|
||||
id = "completed_session_1",
|
||||
gymId = "gym1",
|
||||
date = "2023-12-31",
|
||||
startTime = "2023-12-31T15:00:00",
|
||||
endTime = "2023-12-31T17:00:00",
|
||||
duration = 7200000,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = "Previous session",
|
||||
createdAt = "2023-12-31T15:00:00",
|
||||
updatedAt = "2023-12-31T17:00:00"
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
BackupClimbSession(
|
||||
id = "active_session_1",
|
||||
gymId = "gym1",
|
||||
date = "2024-01-01",
|
||||
startTime = "2024-01-01T10:00:00",
|
||||
endTime = null,
|
||||
duration = null,
|
||||
status = SessionStatus.ACTIVE,
|
||||
notes = null,
|
||||
createdAt = "2024-01-01T10:00:00",
|
||||
updatedAt = "2024-01-01T10:00:00",
|
||||
),
|
||||
BackupClimbSession(
|
||||
id = "completed_session_1",
|
||||
gymId = "gym1",
|
||||
date = "2023-12-31",
|
||||
startTime = "2023-12-31T15:00:00",
|
||||
endTime = "2023-12-31T17:00:00",
|
||||
duration = 7200000,
|
||||
status = SessionStatus.COMPLETED,
|
||||
notes = "Previous session",
|
||||
createdAt = "2023-12-31T15:00:00",
|
||||
updatedAt = "2023-12-31T17:00:00",
|
||||
),
|
||||
)
|
||||
|
||||
// Simulate filtering that would happen in createBackupFromRepository
|
||||
val sessionsForSync = allLocalSessions.filter { it.status != SessionStatus.ACTIVE }
|
||||
@@ -493,18 +493,18 @@ class SyncMergeLogicTest {
|
||||
|
||||
// Active session should be excluded
|
||||
assertFalse(
|
||||
"Should not contain active session in sync",
|
||||
sessionsForSync.any {
|
||||
it.id == "active_session_1" && it.status == SessionStatus.ACTIVE
|
||||
}
|
||||
"Should not contain active session in sync",
|
||||
sessionsForSync.any {
|
||||
it.id == "active_session_1" && it.status == SessionStatus.ACTIVE
|
||||
},
|
||||
)
|
||||
|
||||
// Completed session should be included
|
||||
assertTrue(
|
||||
"Should contain completed session in sync",
|
||||
sessionsForSync.any {
|
||||
it.id == "completed_session_1" && it.status == SessionStatus.COMPLETED
|
||||
}
|
||||
"Should contain completed session in sync",
|
||||
sessionsForSync.any {
|
||||
it.id == "completed_session_1" && it.status == SessionStatus.COMPLETED
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.atridad.ascently
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class UtilityTests {
|
||||
|
||||
@@ -75,13 +75,13 @@ class UtilityTests {
|
||||
@Test
|
||||
fun testClimbingStatistics() {
|
||||
val attempts =
|
||||
listOf(
|
||||
AttemptData("SUCCESS", 120),
|
||||
AttemptData("FALL", 90),
|
||||
AttemptData("SUCCESS", 150),
|
||||
AttemptData("FLASH", 60),
|
||||
AttemptData("FALL", 110)
|
||||
)
|
||||
listOf(
|
||||
AttemptData("SUCCESS", 120),
|
||||
AttemptData("FALL", 90),
|
||||
AttemptData("SUCCESS", 150),
|
||||
AttemptData("FLASH", 60),
|
||||
AttemptData("FALL", 110),
|
||||
)
|
||||
|
||||
val stats = calculateAttemptStatistics(attempts)
|
||||
|
||||
@@ -163,23 +163,23 @@ class UtilityTests {
|
||||
@Test
|
||||
fun testSearchFiltering() {
|
||||
val problems =
|
||||
listOf(
|
||||
ProblemData(
|
||||
"id1",
|
||||
"Crimpy Problem",
|
||||
"BOULDER",
|
||||
"V5",
|
||||
listOf("crimpy", "overhang")
|
||||
),
|
||||
ProblemData("id2", "Easy Route", "ROPE", "5.6", listOf("beginner", "slab")),
|
||||
ProblemData(
|
||||
"id3",
|
||||
"Hard Boulder",
|
||||
"BOULDER",
|
||||
"V10",
|
||||
listOf("powerful", "roof")
|
||||
)
|
||||
)
|
||||
listOf(
|
||||
ProblemData(
|
||||
"id1",
|
||||
"Crimpy Problem",
|
||||
"BOULDER",
|
||||
"V5",
|
||||
listOf("crimpy", "overhang"),
|
||||
),
|
||||
ProblemData("id2", "Easy Route", "ROPE", "5.6", listOf("beginner", "slab")),
|
||||
ProblemData(
|
||||
"id3",
|
||||
"Hard Boulder",
|
||||
"BOULDER",
|
||||
"V10",
|
||||
listOf("powerful", "roof"),
|
||||
),
|
||||
)
|
||||
|
||||
val boulderProblems = filterByClimbType(problems, "BOULDER")
|
||||
assertEquals(2, boulderProblems.size)
|
||||
@@ -207,20 +207,20 @@ class UtilityTests {
|
||||
@Test
|
||||
fun testBackupValidation() {
|
||||
val validBackup =
|
||||
BackupData(
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
exportedAt = "2024-01-01T10:00:00Z",
|
||||
dataCount = 5
|
||||
)
|
||||
BackupData(
|
||||
version = "2.0",
|
||||
formatVersion = "2.0",
|
||||
exportedAt = "2024-01-01T10:00:00Z",
|
||||
dataCount = 5,
|
||||
)
|
||||
|
||||
val invalidBackup =
|
||||
BackupData(
|
||||
version = "1.0",
|
||||
formatVersion = "2.0",
|
||||
exportedAt = "invalid-date",
|
||||
dataCount = -1
|
||||
)
|
||||
BackupData(
|
||||
version = "1.0",
|
||||
formatVersion = "2.0",
|
||||
exportedAt = "invalid-date",
|
||||
dataCount = -1,
|
||||
)
|
||||
|
||||
assertTrue(isValidBackup(validBackup))
|
||||
assertFalse(isValidBackup(invalidBackup))
|
||||
@@ -258,10 +258,10 @@ class UtilityTests {
|
||||
val successRate = (successful.toDouble() / attempts.size) * 100
|
||||
|
||||
return AttemptStatistics(
|
||||
totalAttempts = attempts.size,
|
||||
successfulAttempts = successful,
|
||||
successRate = successRate,
|
||||
averageDuration = avgDuration
|
||||
totalAttempts = attempts.size,
|
||||
successfulAttempts = successful,
|
||||
successRate = successRate,
|
||||
averageDuration = avgDuration,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -301,8 +301,8 @@ class UtilityTests {
|
||||
}
|
||||
|
||||
private fun filterByClimbType(
|
||||
problems: List<ProblemData>,
|
||||
climbType: String
|
||||
problems: List<ProblemData>,
|
||||
climbType: String,
|
||||
): List<ProblemData> {
|
||||
return problems.filter { it.climbType == climbType }
|
||||
}
|
||||
@@ -312,9 +312,9 @@ class UtilityTests {
|
||||
}
|
||||
|
||||
private fun filterByDifficultyRange(
|
||||
problems: List<ProblemData>,
|
||||
minGrade: String,
|
||||
maxGrade: String
|
||||
problems: List<ProblemData>,
|
||||
minGrade: String,
|
||||
maxGrade: String,
|
||||
): List<ProblemData> {
|
||||
return problems.filter { problem ->
|
||||
if (problem.climbType == "BOULDER" && problem.difficulty.startsWith("V")) {
|
||||
@@ -329,17 +329,17 @@ class UtilityTests {
|
||||
}
|
||||
|
||||
private fun mergeData(
|
||||
local: Map<String, String>,
|
||||
server: Map<String, String>
|
||||
local: Map<String, String>,
|
||||
server: Map<String, String>,
|
||||
): Map<String, String> {
|
||||
return (local.keys + server.keys).associateWith { key -> server[key] ?: local[key]!! }
|
||||
}
|
||||
|
||||
private fun isValidBackup(backup: BackupData): Boolean {
|
||||
return backup.version == "2.0" &&
|
||||
backup.formatVersion == "2.0" &&
|
||||
backup.exportedAt.matches(Regex("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")) &&
|
||||
backup.dataCount >= 0
|
||||
backup.formatVersion == "2.0" &&
|
||||
backup.exportedAt.matches(Regex("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")) &&
|
||||
backup.dataCount >= 0
|
||||
}
|
||||
|
||||
// Data classes for testing
|
||||
@@ -347,24 +347,24 @@ class UtilityTests {
|
||||
data class AttemptData(val result: String, val duration: Int)
|
||||
|
||||
data class AttemptStatistics(
|
||||
val totalAttempts: Int,
|
||||
val successfulAttempts: Int,
|
||||
val successRate: Double,
|
||||
val averageDuration: Double
|
||||
val totalAttempts: Int,
|
||||
val successfulAttempts: Int,
|
||||
val successRate: Double,
|
||||
val averageDuration: Double,
|
||||
)
|
||||
|
||||
data class ProblemData(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val climbType: String,
|
||||
val difficulty: String,
|
||||
val tags: List<String>
|
||||
val id: String,
|
||||
val name: String,
|
||||
val climbType: String,
|
||||
val difficulty: String,
|
||||
val tags: List<String>,
|
||||
)
|
||||
|
||||
data class BackupData(
|
||||
val version: String,
|
||||
val formatVersion: String,
|
||||
val exportedAt: String,
|
||||
val dataCount: Int
|
||||
val version: String,
|
||||
val formatVersion: String,
|
||||
val exportedAt: String,
|
||||
val dataCount: Int,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user