zig-pay/src/data/payments.zig
2025-08-07 21:41:44 -03:00

604 lines
26 KiB
Zig

const std = @import("std");
const expect = std.testing.expect;
const heap = std.heap;
const hash_map = std.hash_map;
const mem = std.mem;
const Thread = std.Thread;
const Mutex = Thread.Mutex;
const Allocator = mem.Allocator;
const testing = std.testing;
const DateTime = @import("things").DateTime;
const SubscriberFunc = fn (payment: *Payment) void;
pub const PaymentIntegrationStatus = enum { processing, processed, not_integrated };
pub const Payment = struct {
id: [36]u8,
amount: f64,
requested_at: DateTime,
integration_status: PaymentIntegrationStatus = .processing,
processed_by: u64 = undefined,
pub fn getIntegrationStatus(self: *const Payment) []const u8 {
return switch (self.integration_status) {
.processing => "processing",
.processed => "processed",
.not_integrated => "not integrated"
};
}
};
pub const PaymentsSummary = struct {
total_payments_processed: usize,
total_value: f64,
};
pub const PaymentsIntegrationSummary = struct {
default_total_payments_processed: usize,
default_total_value: f64,
fallback_total_payments_processed: usize,
fallback_total_value: f64,
};
const CONTAINER_INCREASE_RATE = 32;
const MAX_CONTAINER_INCREASE = 32 * CONTAINER_INCREASE_RATE;
inline fn calculateMaxIndexes(tee: usize) usize {
return ((tee / CONTAINER_INCREASE_RATE) + 1);
}
pub inline fn calculateNecessaryMemory(tee: usize) usize {
return (@sizeOf(Payment) * tee) + (@sizeOf(IndexContainerUnsafe) * calculateMaxIndexes(tee)) + (calculateMaxIndexes(tee) * MAX_CONTAINER_INCREASE);
}
inline fn newContainerSize(c: usize) usize {
return c + CONTAINER_INCREASE_RATE;
}
const IndexContainerUnsafe = struct {
len: usize = 0,
container_size: usize = CONTAINER_INCREASE_RATE,
container: []*const Payment = undefined,
allocator: Allocator,
pub fn init(allocator: Allocator) !IndexContainerUnsafe {
const container = try allocator.alloc(*const Payment, CONTAINER_INCREASE_RATE);
return IndexContainerUnsafe{ .container = container, .allocator = allocator };
}
pub fn add(self: *IndexContainerUnsafe, payment: *const Payment) !void {
if (self.len == self.container_size) {
const new_container_size = newContainerSize(self.len);
self.container = try self.allocator.realloc(self.container, new_container_size);
self.container_size = new_container_size;
}
self.container[self.len] = payment;
self.len += 1;
}
pub fn find(self: *IndexContainerUnsafe, target: []const u8) ?*const Payment {
for (0..self.len) |i| {
if (mem.eql(u8, target, &self.container[i].id))
return self.container[i];
}
return null;
}
pub fn reset(self: *IndexContainerUnsafe) void {
self.len = 0;
}
pub fn deinit(self: *IndexContainerUnsafe) void {
self.len = 0;
self.container_size = 0;
self.allocator.free(self.container);
}
};
pub const PaymentsRepository = struct {
len: usize = 0,
max_indexes: usize = 1,
database_size: usize,
payments: []Payment,
indexes: []IndexContainerUnsafe,
mutex: Mutex,
allocator: Allocator,
subscribers_insert: [2]*const SubscriberFunc = undefined,
subscribers_insert_len: usize = 0,
pub fn init(allocator: Allocator, database_size: usize) !PaymentsRepository {
const payments = try allocator.alloc(Payment, database_size);
const max_indexes = calculateMaxIndexes(database_size);
var indexes = try allocator.alloc(IndexContainerUnsafe, max_indexes);
for (0..indexes.len) |i| {
indexes[i] = try IndexContainerUnsafe.init(allocator);
}
return PaymentsRepository{ .allocator = allocator, .mutex = Mutex{}, .database_size = database_size, .payments = payments, .indexes = indexes, .max_indexes = max_indexes };
}
pub fn subscribeInsert(self: *PaymentsRepository, func: *const SubscriberFunc) void {
self.subscribers_insert[self.subscribers_insert_len] = func;
self.subscribers_insert_len += 1;
}
pub fn insert(self: *PaymentsRepository, payment: Payment) !void {
self.mutex.lock();
defer self.mutex.unlock();
if (self.len == self.database_size)
return Allocator.Error.OutOfMemory;
self.payments[self.len] = payment;
const id_hash = self.hash(&payment.id);
try self.indexes[id_hash].add(&self.payments[self.len]);
for (self.subscribers_insert[0..self.subscribers_insert_len]) |func| {
func(&self.payments[self.len]);
}
self.len += 1;
}
pub fn findById(self: *PaymentsRepository, id: []const u8) ?*const Payment {
self.mutex.lock();
defer self.mutex.unlock();
if (self.len == 0)
return null;
const id_hash = self.hash(id);
const ic = self.indexes[id_hash].find(id);
return ic;
}
pub fn reset(self: *PaymentsRepository) void {
self.mutex.lock();
defer self.mutex.unlock();
self.len = 0;
for (0..self.indexes.len) |i| {
self.indexes[i].reset();
}
}
pub fn periodSummary(self: *PaymentsRepository, from: ?DateTime, to: ?DateTime) PaymentsSummary {
self.mutex.lock();
const len = self.len;
self.mutex.unlock();
var payments_summary = PaymentsSummary{ .total_payments_processed = 0, .total_value = 0 };
if (from != null and to != null) {
for (0..len) |i| {
const payment = self.payments[i];
if (payment.requested_at.gt(from.?) and payment.requested_at.lt(to.?)) {
payments_summary.total_payments_processed += 1;
payments_summary.total_value += payment.amount;
}
}
} else if (from != null) {
for (0..len) |i| {
const payment = self.payments[i];
if (payment.requested_at.gt(from.?)) {
payments_summary.total_payments_processed += 1;
payments_summary.total_value += payment.amount;
}
}
} else if (to != null) {
for (0..len) |i| {
const payment = self.payments[i];
if (payment.requested_at.lt(to.?)) {
payments_summary.total_payments_processed += 1;
payments_summary.total_value += payment.amount;
}
}
} else {
for (0..len) |i| {
payments_summary.total_value += self.payments[i].amount;
}
payments_summary.total_payments_processed = len;
}
return payments_summary;
}
pub fn integrationSummary(self: *PaymentsRepository, from: ?DateTime, to: ?DateTime) PaymentsIntegrationSummary {
self.mutex.lock();
const len = self.len;
self.mutex.unlock();
var summary = PaymentsIntegrationSummary{
.default_total_payments_processed = 0,
.default_total_value = 0,
.fallback_total_payments_processed = 0,
.fallback_total_value = 0,
};
if (from != null and to != null) {
for (0..len) |i| {
const payment = self.payments[i];
if (payment.requested_at.gt(from.?) and payment.requested_at.lt(to.?)) {
if (payment.integration_status != .processed)
continue;
if (payment.processed_by == 1) {
summary.default_total_payments_processed += 1;
summary.default_total_value += payment.amount;
} else {
summary.fallback_total_payments_processed += 1;
summary.fallback_total_value += payment.amount;
}
}
}
} else if (from != null) {
for (0..len) |i| {
const payment = self.payments[i];
if (payment.requested_at.gt(from.?)) {
if (payment.integration_status != .processed)
continue;
if (payment.processed_by == 1) {
summary.default_total_payments_processed += 1;
summary.default_total_value += payment.amount;
} else {
summary.fallback_total_payments_processed += 1;
summary.fallback_total_value += payment.amount;
}
}
}
} else if (to != null) {
for (0..len) |i| {
const payment = self.payments[i];
if (payment.requested_at.lt(to.?)) {
if (payment.integration_status != .processed)
continue;
if (payment.processed_by == 1) {
summary.default_total_payments_processed += 1;
summary.default_total_value += payment.amount;
} else {
summary.fallback_total_payments_processed += 1;
summary.fallback_total_value += payment.amount;
}
}
}
} else {
for (0..len) |i| {
const payment = self.payments[i];
if (payment.integration_status != .processed)
continue;
if (payment.processed_by == 1) {
summary.default_total_payments_processed += 1;
summary.default_total_value += payment.amount;
} else {
summary.fallback_total_payments_processed += 1;
summary.fallback_total_value += payment.amount;
}
}
}
return summary;
}
fn hash(self: *PaymentsRepository, s: []const u8) usize {
std.debug.assert(self.max_indexes > 0);
return hash_map.hashString(s) % self.max_indexes;
}
pub fn deinit(self: *PaymentsRepository) void {
self.mutex.lock();
defer self.mutex.unlock();
for (self.indexes) |ic| {
@constCast(&ic).deinit();
}
self.allocator.free(self.indexes);
self.len = 0;
self.database_size = 0;
self.max_indexes = 0;
self.allocator.free(self.payments);
}
};
fn fakeGuid(end: u32) [36]u8 {
var guid: [36]u8 = .{0} ** 36;
mem.writeInt(u32, guid[0..4], end, .little);
return guid;
}
test "expect add payment to index container" {
var index_container = try IndexContainerUnsafe.init(testing.allocator);
defer index_container.deinit();
const payment = Payment{ .id = fakeGuid(1), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") };
try index_container.add(&payment);
try index_container.add(&payment);
try index_container.add(&payment);
try index_container.add(&payment);
try expect(index_container.len == 4);
}
test "expect find inserted payment" {
var index_container = try IndexContainerUnsafe.init(testing.allocator);
defer index_container.deinit();
try index_container.add(&Payment{ .id = fakeGuid(1), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") });
try index_container.add(&Payment{ .id = fakeGuid(2), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") });
try index_container.add(&Payment{ .id = fakeGuid(3), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") });
try index_container.add(&Payment{ .id = fakeGuid(4), .amount = 23.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") });
try index_container.add(&Payment{ .id = fakeGuid(5), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") });
const payment_target = index_container.find(&fakeGuid(4));
try expect(payment_target.?.amount == 23.0);
}
test "expect return null payment not exists" {
var index_container = try IndexContainerUnsafe.init(testing.allocator);
defer index_container.deinit();
try index_container.add(&Payment{ .id = fakeGuid(1), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") });
const payment_target = index_container.find(&fakeGuid(4));
try expect(payment_target == null);
}
test "expect allocate memory when container is full" {
const buffer: []u8 = try testing.allocator.alloc(u8, @sizeOf(*void) * 1000);
defer testing.allocator.free(buffer);
var fba = heap.FixedBufferAllocator.init(buffer);
const allocator = fba.allocator();
var index_container = try IndexContainerUnsafe.init(allocator);
defer index_container.deinit();
for (0..923) |i| {
try (&index_container).add(&Payment{ .id = fakeGuid(@intCast(i)), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") });
}
try expect(index_container.container_size == ((923 / CONTAINER_INCREASE_RATE) + 1) * CONTAINER_INCREASE_RATE);
}
test "expect OutOfMemory error when allocator not has memory" {
const buffer: []u8 = try testing.allocator.alloc(u8, @sizeOf(*void) * 109);
defer testing.allocator.free(buffer);
var fba = heap.FixedBufferAllocator.init(buffer);
const allocator = fba.allocator();
var index_container = try IndexContainerUnsafe.init(allocator);
defer index_container.deinit();
for (0..101) |i| {
(&index_container).add(&Payment{ .id = fakeGuid(@intCast(i)), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") }) catch |err| {
try expect(err == Allocator.Error.OutOfMemory);
return;
};
}
try expect(false);
}
test "expect insert payment into repository" {
var repository = try PaymentsRepository.init(testing.allocator, 100);
defer repository.deinit();
const payment1 = Payment{ .id = fakeGuid(1), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") };
const payment2 = Payment{ .id = fakeGuid(2), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") };
try repository.insert(payment1);
try repository.insert(payment2);
try expect(repository.len == 2);
}
test "expect OutOfMemory error when database is full" {
var repository = try PaymentsRepository.init(testing.allocator, 100);
defer (&repository).deinit();
for (0..101) |i| {
const payment = Payment{ .id = fakeGuid(@intCast(i)), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") };
(&repository).insert(payment) catch |err| {
try expect(err == Allocator.Error.OutOfMemory);
return;
};
}
try expect(false);
}
test "expect return payment if exists" {
var repository: *PaymentsRepository = @constCast(&try PaymentsRepository.init(testing.allocator, 100));
defer repository.deinit();
try repository.insert(Payment{ .id = fakeGuid(1), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(2), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(3), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(23), .amount = 42.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(4), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(7), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(10), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
const payment_result = repository.findById(&fakeGuid(23));
try expect(payment_result.?.amount == 42);
}
test "expect return null if payment not exists" {
var repository: *PaymentsRepository = @constCast(&try PaymentsRepository.init(testing.allocator, 100));
defer repository.deinit();
try repository.insert(Payment{ .id = fakeGuid(1), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(2), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(3), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(23), .amount = 42.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(4), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(7), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(10), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
const payment_result = repository.findById(&fakeGuid(42));
try expect(payment_result == null);
}
fn testConcurrenceInsert(repository: *PaymentsRepository, interations: usize) !void {
for (0..interations) |i| {
try repository.insert(Payment{ .id = fakeGuid(@intCast(i)), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2011-10-05T14:48:00.000Z") });
}
}
fn testConcurrenceFind(repository: *PaymentsRepository, interations: usize) void {
for (0..interations) |i| {
_ = repository.findById(&fakeGuid(@intCast(i)));
}
}
test "expect avoid concurrency" {
const interations = 100;
const buffer: []u8 = try testing.allocator.alloc(u8, calculateNecessaryMemory(interations));
defer testing.allocator.free(buffer);
var fba = heap.FixedBufferAllocator.init(buffer);
const allocator = fba.allocator();
var repository: *PaymentsRepository = @constCast(&try PaymentsRepository.init(allocator, interations));
defer repository.deinit();
const threadInsert1 = try Thread.spawn(.{}, testConcurrenceInsert, .{ repository, interations / 2 });
const threadInsert2 = try Thread.spawn(.{}, testConcurrenceInsert, .{ repository, interations / 2 });
const threadFind = try Thread.spawn(.{}, testConcurrenceFind, .{ repository, interations });
threadInsert1.join();
threadInsert2.join();
threadFind.join();
try expect(repository.len == interations);
}
test "expect reset repository and indexes" {
var repository: *PaymentsRepository = @constCast(&try PaymentsRepository.init(testing.allocator, 100));
defer repository.deinit();
try repository.insert(Payment{ .id = fakeGuid(1), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(2), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(3), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(23), .amount = 42.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(4), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(7), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(10), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
repository.reset();
try expect(repository.len == 0);
for (repository.indexes) |ic| {
try expect(ic.len == 0);
}
}
test "expect summary return correct value" {
var repository = try PaymentsRepository.init(testing.allocator, 100);
defer repository.deinit();
try repository.insert(Payment{ .id = fakeGuid(1), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(2), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(3), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(23), .amount = 42.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(4), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(7), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(10), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.000Z") });
const summary = repository.periodSummary(null, null);
try expect(summary.total_payments_processed == 7);
try expect(summary.total_value == 102.0);
}
test "expect summary return correct value with filter from" {
var repository = try PaymentsRepository.init(testing.allocator, 100);
defer repository.deinit();
const from = try DateTime.ParseFromIso("2025-10-05T14:48:00.030Z");
try repository.insert(Payment{ .id = fakeGuid(1), .amount = 42.0, .requested_at = try DateTime.ParseFromIso("2024-10-05T13:47:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(2), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2024-10-05T14:48:00.033Z") });
try repository.insert(Payment{ .id = fakeGuid(3), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-09-05T14:48:00.100Z") });
try repository.insert(Payment{ .id = fakeGuid(4), .amount = 23.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.030Z") });
try repository.insert(Payment{ .id = fakeGuid(7), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.100Z") });
try repository.insert(Payment{ .id = fakeGuid(6), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:50:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(5), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-11-05T14:48:00.030Z") });
const summary = repository.periodSummary(from, null);
try expect(summary.total_payments_processed == 4);
try expect(summary.total_value == 53.0);
}
test "expect summary return correct correct value with filter to" {
var repository = try PaymentsRepository.init(testing.allocator, 100);
defer repository.deinit();
const to = try DateTime.ParseFromIso("2025-10-05T14:48:00.030Z");
try repository.insert(Payment{ .id = fakeGuid(1), .amount = 42.0, .requested_at = try DateTime.ParseFromIso("2024-10-05T13:47:00.000Z") }); // x
try repository.insert(Payment{ .id = fakeGuid(2), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2024-10-05T14:48:00.033Z") }); // x
try repository.insert(Payment{ .id = fakeGuid(3), .amount = 11.0, .requested_at = try DateTime.ParseFromIso("2025-09-05T14:48:00.100Z") }); // x
try repository.insert(Payment{ .id = fakeGuid(4), .amount = 23.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.030Z") }); // x
//
try repository.insert(Payment{ .id = fakeGuid(6), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:50:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(7), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.100Z") });
try repository.insert(Payment{ .id = fakeGuid(5), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-11-05T14:48:00.030Z") });
const summary = repository.periodSummary(null, to);
try expect(summary.total_payments_processed == 4);
try expect(summary.total_value == 86.0);
}
test "expect summary return correct value with filter from and to" {
var repository = try PaymentsRepository.init(testing.allocator, 100);
defer repository.deinit();
const from = try DateTime.ParseFromIso("2025-10-05T14:48:00.030Z");
const to = try DateTime.ParseFromIso("2025-10-05T14:48:00.100Z");
try repository.insert(Payment{ .id = fakeGuid(1), .amount = 42.0, .requested_at = try DateTime.ParseFromIso("2024-10-05T13:47:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(2), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2024-10-05T14:48:00.033Z") });
try repository.insert(Payment{ .id = fakeGuid(3), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-09-05T14:48:00.100Z") });
try repository.insert(Payment{ .id = fakeGuid(4), .amount = 23.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.030Z") }); // x
try repository.insert(Payment{ .id = fakeGuid(5), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-11-05T14:48:00.030Z") });
try repository.insert(Payment{ .id = fakeGuid(6), .amount = 10.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:50:00.000Z") });
try repository.insert(Payment{ .id = fakeGuid(7), .amount = 42.0, .requested_at = try DateTime.ParseFromIso("2025-10-05T14:48:00.100Z") }); // x
const summary = repository.periodSummary(from, to);
try expect(summary.total_payments_processed == 2);
try expect(summary.total_value == 65.0);
}