Dated the demo cons in the Persistence package from the weekend after seeding, and re-dated the ones already seeded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
javierandClaude Opus 5.5 committed 2026-10-25 12:47:23 +01:00
1 parent e0524da525
commit 3a3d2619ad
6 files changed
+77 -32

No files matched your search

@@ -29,7 +29,7 @@ struct CreateCommunity: AsyncMigration {
.unique(on: "user_id", "series_slug", "issue")
.create()
for (event, shops) in Seed.events {
for (event, shops) in Seed.events(after: .init()) {
let record = EventRecord(event)
try await record.save(on: database)
@@ -0,0 +1,21 @@
import FluentKit
import Foundation
/// Re-dates the demo cons from the first weekend after it runs, for databases seeded when ``CreateCommunity`` used fixed dates.
struct RedateEvents: AsyncMigration {
// MARK: Methods
func prepare(on database: Database) async throws {
for (event, _) in Seed.events(after: .init()) {
try await EventRecord.query(on: database)
.filter(\.$slug == event.slug)
.set(\.$startsOn, to: event.startsOn)
.set(\.$endsOn, to: event.endsOn)
.update()
}
}
func revert(on database: Database) async throws {}
}
@@ -158,29 +158,36 @@ enum Seed {
),
]
/// The demo cons, soonest first, each with the slugs of the shops trading there.
static let events: [(event: Event, shops: [String])] = [
(.init(slug: "dutch-comic-con", name: "Dutch Comic Con", venue: "Jaarbeurs, Utrecht", startsOn: day(2026, 9, 26), endsOn: day(2026, 9, 27)),
["kestrel-row-comics", "silverfish-comics", "third-panel-supply"]),
(.init(slug: "stripdagen-haarlem", name: "Stripdagen Haarlem", venue: "Grote Markt, Haarlem", startsOn: day(2026, 10, 3), endsOn: day(2026, 10, 4)),
["silverfish-comics"]),
(.init(slug: "comic-con-amsterdam", name: "Comic Con Amsterdam", venue: "RAI, Amsterdam", startsOn: day(2026, 11, 21), endsOn: day(2026, 11, 22)),
["kestrel-row-comics", "inkwell-and-staple"]),
(.init(slug: "comic-con-brussels", name: "Comic Con Brussels", venue: "Tour & Taxis, Brussels", startsOn: day(2026, 12, 5), endsOn: day(2026, 12, 6)),
["third-panel-supply"]),
]
/// The demo cons, soonest first, each with the slugs of the shops trading there, dated from the first weekend after `date`.
static func events(
after date: Date
) -> [(event: Event, shops: [String])] {
[
("dutch-comic-con", "Dutch Comic Con", "Jaarbeurs, Utrecht", 0, ["kestrel-row-comics", "silverfish-comics", "third-panel-supply"]),
("stripdagen-haarlem", "Stripdagen Haarlem", "Grote Markt, Haarlem", 1, ["silverfish-comics"]),
("comic-con-amsterdam", "Comic Con Amsterdam", "RAI, Amsterdam", 8, ["kestrel-row-comics", "inkwell-and-staple"]),
("comic-con-brussels", "Comic Con Brussels", "Tour & Taxis, Brussels", 10, ["third-panel-supply"]),
]
.map { slug, name, venue, week, shops in
let (startsOn, endsOn) = weekend(week, after: date)
/// Noon on a day in Amsterdam, where the demo cons are dated.
static func day(
_ year: Int,
_ month: Int,
_ day: Int
) -> Date {
return (.init(slug: slug, name: name, venue: venue, startsOn: startsOn, endsOn: endsOn), shops)
}
}
/// Noon on the Saturday and Sunday `week` weeks on from the first weekend after `date`, in Amsterdam, where the demo cons are held.
static func weekend(
_ week: Int,
after date: Date
) -> (startsOn: Date, endsOn: Date) {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "Europe/Amsterdam") ?? .gmt
return calendar.date(from: .init(year: year, month: month, day: day, hour: 12)) ?? .distantPast
let saturday = calendar.nextDate(after: date, matching: .init(hour: 12, weekday: 7), matchingPolicy: .nextTime) ?? date
let startsOn = calendar.date(byAdding: .weekOfYear, value: week, to: saturday) ?? saturday
return (startsOn, calendar.date(byAdding: .day, value: 1, to: startsOn) ?? startsOn)
}
/// The runs the demo listings belong to.
@@ -33,6 +33,7 @@ public struct PrepareDB: Sendable {
CreatePasskey(),
CreateCollecting(),
CreateCommunity(),
RedateEvents(),
])
}
@@ -46,13 +46,13 @@ struct CommunityRepositoryTests {
let clock = Clock()
let collecting = CollectingRepository(fluent: fluent, now: clock.now)
// 22 September 2026: every demo con is still to come.
clock.advance(by: Seed.day(2026, 9, 22).timeIntervalSince(clock.now()))
// The present, which every demo con is seeded after.
clock.advance(by: Date().timeIntervalSince(clock.now()))
let all = try await collecting.upcomingEvents()
// Sunday 27 September, the con's last day: still on. Two weeks later it is over, and so is Stripdagen.
clock.advance(by: 5 * 86_400)
// The first con's last day: still on. Two weeks later it is over, and so is Stripdagen.
clock.advance(by: try #require(all.first).endsOn.timeIntervalSince(clock.now()))
let lastDay = try await collecting.upcomingEvents()
@@ -67,17 +67,35 @@ struct CommunityRepositoryTests {
}
}
@Test
func `dates the demo cons on weekends, from the first one after seeding`() {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "Europe/Amsterdam") ?? .gmt
// Monday 21 September 2026: the dates the prototype first shipped with.
let days = Seed.events(after: Date(timeIntervalSince1970: 1_790_000_000)).map { entry in
[entry.event.startsOn, entry.event.endsOn]
.map { calendar.dateComponents([.month, .day, .hour], from: $0) }
.map { "\($0.month ?? 0)/\($0.day ?? 0) \($0.hour ?? 0):00" }
}
#expect(days == [
["9/26 12:00", "9/27 12:00"],
["10/3 12:00", "10/4 12:00"],
["11/21 12:00", "11/22 12:00"],
["12/5 12:00", "12/6 12:00"],
])
}
@Test
func `lets a seller pick the events their shop trades at`() async throws {
try await withDatabase { fluent in
let clock = Clock()
let collecting = CollectingRepository(fluent: fluent, now: clock.now)
let collecting = CollectingRepository(fluent: fluent)
let accounts = AccountRepository(fluent: fluent)
let joost = try await accounts.account(email: "joost@kestrelrow.example")
let mara = try await accounts.account(email: "mara.v@example.com")
clock.advance(by: Seed.day(2026, 9, 22).timeIntervalSince(clock.now()))
try await collecting.setEvents(["stripdagen-haarlem", "comic-con-amsterdam", "no-such-event"], forShopOwnedBy: joost)
// An account without a shop changes nothing.
try await collecting.setEvents(["dutch-comic-con"], forShopOwnedBy: mara)
@@ -246,9 +246,8 @@ private extension AccountAndCollectingControllerTests {
}
}
/// Runs the body against the session, catalogue, account, and collecting routes, over a migrated, seeded in-memory database, judging events by `now`.
/// Runs the body against the session, catalogue, account, and collecting routes, over a migrated, seeded in-memory database.
func withApp(
now: @escaping @Sendable () -> Date = { .init() },
_ body: @escaping @Sendable (any TestClientProtocol) async throws -> Void
) async throws {
let fluent = try Service(
@@ -266,7 +265,7 @@ private extension AccountAndCollectingControllerTests {
let accounts = AccountRepository(fluent: fluent)
let catalogue = CatalogueRepository(fluent: fluent)
let collecting = CollectingRepository(fluent: fluent, now: now)
let collecting = CollectingRepository(fluent: fluent)
let sessions = SessionStore(trade: .init(fluent: fluent), collecting: collecting)
let router = Router(context: WebsiteRequestContext.self)
@@ -309,8 +308,7 @@ extension AccountAndCollectingControllerTests {
@Test
func `serves the events, and narrows a search to the copies to pick up at one`() async throws {
// 22 September 2026: every demo con is still to come.
try await withApp(now: { Date(timeIntervalSince1970: 1_790_071_200) }) { client in
try await withApp { client in
try await client.execute(uri: "/events", method: .get) { response in
let body = String(buffer: response.body)