feat(domain): update MemoryCapsuleGenerator + test

This commit is contained in:
null 2026-06-28 16:34:57 -05:00
parent b9828b60c5
commit 4ee600125d
2 changed files with 24 additions and 12 deletions

View File

@ -23,6 +23,10 @@ import java.time.temporal.ChronoUnit
* - Reveal-derived titles use only category metadata and the fact that a reveal happened.
* - Manual notes are accepted as already-encrypted ciphertext; this object does not encrypt.
* - Tags are built from category, source type, and date context only.
*
* Determinism: every factory takes an injectable `createdAtMillis` (defaulting to the wall clock)
* so that, given identical inputs, the produced capsule is identical. Tests pin it; production omits
* it to stamp the real creation time.
*/
object MemoryCapsuleGenerator {
@ -67,7 +71,8 @@ object MemoryCapsuleGenerator {
event: MemoryDateIdeaEvent,
noteCiphertext: String? = null,
photoStoragePath: String? = null,
now: LocalDate = LocalDate.now()
now: LocalDate = LocalDate.now(),
createdAtMillis: Long = System.currentTimeMillis()
): MemoryCapsule {
val date = event.scheduledDate ?: now
val displayCategory = event.category.takeIf { it.isNotBlank() } ?: DATE_CATEGORY
@ -92,7 +97,7 @@ object MemoryCapsuleGenerator {
),
source = MemoryCapsuleSource.DATE_IDEA,
sourceRefId = event.dateIdeaId,
createdAt = System.currentTimeMillis()
createdAt = createdAtMillis
)
}
@ -106,7 +111,8 @@ object MemoryCapsuleGenerator {
authorId: String,
event: MemoryRevealEvent,
noteCiphertext: String? = null,
photoStoragePath: String? = null
photoStoragePath: String? = null,
createdAtMillis: Long = System.currentTimeMillis()
): MemoryCapsule {
val displayCategory = event.categoryId?.takeIf { it.isNotBlank() } ?: REVEAL_CATEGORY
val title = "A moment we revealed together${categoryFragment(displayCategory)}"
@ -127,7 +133,7 @@ object MemoryCapsuleGenerator {
),
source = MemoryCapsuleSource.REVEAL,
sourceRefId = event.questionId,
createdAt = System.currentTimeMillis()
createdAt = createdAtMillis
)
}
@ -139,7 +145,8 @@ object MemoryCapsuleGenerator {
authorId: String,
event: MemoryRecapEvent,
noteCiphertext: String? = null,
photoStoragePath: String? = null
photoStoragePath: String? = null,
createdAtMillis: Long = System.currentTimeMillis()
): MemoryCapsule {
val displayCategory = event.favoriteCategory?.takeIf { it.isNotBlank() } ?: RECAP_CATEGORY
val weekNumber = weekNumberLabel(event.weekStart, event.weekEnd)
@ -162,7 +169,7 @@ object MemoryCapsuleGenerator {
),
source = MemoryCapsuleSource.WEEKLY_RECAP,
sourceRefId = event.recapId,
createdAt = System.currentTimeMillis()
createdAt = createdAtMillis
)
}
@ -174,7 +181,8 @@ object MemoryCapsuleGenerator {
authorId: String,
event: MemoryChallengeEvent,
noteCiphertext: String? = null,
photoStoragePath: String? = null
photoStoragePath: String? = null,
createdAtMillis: Long = System.currentTimeMillis()
): MemoryCapsule {
val displayCategory = event.challengeCategory?.takeIf { it.isNotBlank() } ?: CHALLENGE_CATEGORY
val title = "Challenge completed${categoryFragment(displayCategory)}"
@ -195,7 +203,7 @@ object MemoryCapsuleGenerator {
),
source = MemoryCapsuleSource.CHALLENGE,
sourceRefId = event.challengeId,
createdAt = System.currentTimeMillis()
createdAt = createdAtMillis
)
}
@ -209,7 +217,8 @@ object MemoryCapsuleGenerator {
coupleId: String,
authorId: String,
input: MemoryManualInput,
now: LocalDate = LocalDate.now()
now: LocalDate = LocalDate.now(),
createdAtMillis: Long = System.currentTimeMillis()
): MemoryCapsule {
val displayCategory = input.category.takeIf { it.isNotBlank() } ?: DEFAULT_CATEGORY
val title = input.title.takeIf { it.isNotBlank() }
@ -231,7 +240,7 @@ object MemoryCapsuleGenerator {
now = now
),
source = MemoryCapsuleSource.MANUAL,
createdAt = System.currentTimeMillis()
createdAt = createdAtMillis
)
}

View File

@ -425,8 +425,11 @@ class MemoryCapsuleGeneratorTest {
scheduledDate = today
)
val first = MemoryCapsuleGenerator.fromDateIdea(coupleId, authorId, event, now = today)
val second = MemoryCapsuleGenerator.fromDateIdea(coupleId, authorId, event, now = today)
// Pin the clock: the generator documents itself as pure/deterministic, so identical inputs
// (including createdAtMillis) must yield identical capsules. Without an injected clock the
// createdAt timestamp made this flaky across millisecond boundaries.
val first = MemoryCapsuleGenerator.fromDateIdea(coupleId, authorId, event, now = today, createdAtMillis = 1_700_000_000_000L)
val second = MemoryCapsuleGenerator.fromDateIdea(coupleId, authorId, event, now = today, createdAtMillis = 1_700_000_000_000L)
assertEquals(first, second)
}