╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ event.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1176 { 1177 const haxy_moment = try evt.currentMoment(repo_opts, &repo); 1178 1179 // get the map of repos 1180 const event_id_to_repo_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->repo")) orelse return error.NotFound; 1181 const event_id_to_repo = try Repo.DB.HashMap(.read_only).init(event_id_to_repo_cursor); 1182 1183 try std.testing.expect(null == try event_id_to_repo.getCursor(hash.hashInt(repo_opts.hash, &repo_event_id))); 1184 1185 // get the repos created by the user 1186 const user_id_to_repo_id_set_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "user-id->repo-id-set")) orelse return error.NotFound; 1187 const user_id_to_repo_id_set = try Repo.DB.HashMap(.read_only).init(user_id_to_repo_id_set_cursor); 1188 1189 const user_repos_cursor = try user_id_to_repo_id_set.getCursor(hash.hashInt(repo_opts.hash, &user_event_id)) orelse return error.NotFound; 1190 const user_repos = try Repo.DB.SortedSet(.read_only).init(user_repos_cursor); 1191 1192 // removing the repo emptied the user's set 1193 try std.testing.expectEqual(0, try user_repos.count()); 1194 } 1195 1196 // 1197 // remove the user 1198 // 1199 1200 const events_to_consume3 = [_]evt.EventWithId{ 1201 .{ 1202 .id = std.fmt.bytesToHex(user_event_id, .lower), 1203 .event = .{ .user = null }, 1204 }, 1205 }; 1206 1207 // commit and consume the removal 1208 try evt.consume(.xit, repo_opts, io, allocator, &repo, evt.events_ref, &events_to_consume3); 1209 1210 { 1211 const haxy_moment = try evt.currentMoment(repo_opts, &repo); 1212 1213 // get the map of users 1214 const event_id_to_user_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->user")) orelse return error.NotFound; 1215 const event_id_to_user = try Repo.DB.HashMap(.read_only).init(event_id_to_user_cursor); 1216 1217 try std.testing.expect(null == try event_id_to_user.getCursor(hash.hashInt(repo_opts.hash, &user_event_id))); 1218 } 1219 } 1220 1221 test "repos and users paginate newest first" { 1222 const io = std.testing.io; 1223 const allocator = std.testing.allocator; 1224 const temp_dir_name = "temp-event-order"; 1225 1226 const cwd = std.Io.Dir.cwd(); 1227 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{}); 1228 if (temp_dir_or_err) |*temp_dir| { 1229 temp_dir.close(io); 1230 try cwd.deleteTree(io, temp_dir_name); 1231 } else |_| {} 1232 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); 1233 defer cwd.deleteTree(io, temp_dir_name) catch {}; 1234 defer temp_dir.close(io); 1235 1236 const cwd_path = try std.process.currentPathAlloc(io, allocator); 1237 defer allocator.free(cwd_path); 1238 const work_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name }); 1239 defer allocator.free(work_path); 1240 1241 const repo_opts: rp.RepoOpts(.xit) = .{ .is_test = true }; 1242 const Repo = rp.Repo(.xit, repo_opts); 1243 var repo = try Repo.init(io, allocator, .{ .path = work_path }); 1244 defer repo.deinit(io, allocator); 1245 1246 var arena = std.heap.ArenaAllocator.init(allocator); 1247 defer arena.deinit(); 1248 1249 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); 1250 const user_id = evt.EventWithId.randomId(prng.random()); 1251 var repo_ids: [4][evt.event_id_size]u8 = undefined; 1252 for (&repo_ids) |*id| id.* = evt.EventWithId.randomId(prng.random()); 1253 1254 var pw_buf: [evt.User.password_hash_max_len]u8 = undefined; 1255 const pw = try evt.User.hashPassword("pw", &pw_buf, io); 1256 1257 // asserts the repo-id-set holds exactly `expected` ids in that order 1258 const Check = struct { 1259 fn order( 1260 comptime DB: type, 1261 comptime hash_kind: hash.HashKind, 1262 moment: DB.HashMap(.read_only), 1263 expected: []const *const [evt.event_id_size]u8, 1264 ) !void { 1265 const cursor = try moment.getCursor(hash.hashInt(hash_kind, "repo-id-set")) orelse return error.NotFound; 1266 const set = try DB.SortedSet(.read_only).init(cursor); 1267 try std.testing.expectEqual(expected.len, try set.count()); 1268 for (expected, 0..) |id, i| { 1269 const kv = (try set.getIndexKeyValuePair(@intCast(i))) orelse return error.NotFound; 1270 // the key is orderKeyDesc ([timestamp][event-id]); its trailing bytes are the id 1271 var order_key: [@sizeOf(u64) + evt.event_id_size]u8 = undefined; 1272 _ = try kv.key_cursor.readBytes(&order_key); 1273 try std.testing.expectEqualSlices(u8, id, order_key[@sizeOf(u64)..]); 1274 } 1275 } 1276 }; 1277 1278 // one user, then four repos, each with its own creation timestamp (user@100, 1279 // repos@101..104) 1280 const events = [_]evt.EventWithId{ 1281 .{ .id = std.fmt.bytesToHex(user_id, .lower), .timestamp = 100, .event = .{ .user = .{ .name = "alice", .display_name = "Alice", .email = "alice@example.test", .password_hash = pw } } }, 1282 .{ .id = std.fmt.bytesToHex(repo_ids[0], .lower), .timestamp = 101, .event = .{ .repo = .{ .user_id = &user_id, .name = "repo0", .description = "d0" } } }, 1283 .{ .id = std.fmt.bytesToHex(repo_ids[1], .lower), .timestamp = 102, .event = .{ .repo = .{ .user_id = &user_id, .name = "repo1", .description = "d1" } } }, 1284 .{ .id = std.fmt.bytesToHex(repo_ids[2], .lower), .timestamp = 103, .event = .{ .repo = .{ .user_id = &user_id, .name = "repo2", .description = "d2" } } }, 1285 .{ .id = std.fmt.bytesToHex(repo_ids[3], .lower), .timestamp = 104, .event = .{ .repo = .{ .user_id = &user_id, .name = "repo3", .description = "d3" } } }, 1286 }; 1287 try evt.consume(.xit, repo_opts, io, allocator, &repo, evt.events_ref, &events); 1288 1289 { 1290 const moment = try evt.currentMoment(repo_opts, &repo); 1291 try Check.order(Repo.DB, repo_opts.hash, moment, &.{ &repo_ids[3], &repo_ids[2], &repo_ids[1], &repo_ids[0] }); 1292 1293 // the single user shows up in its own ordered set 1294 const ucur = try moment.getCursor(hash.hashInt(repo_opts.hash, "user-id-set")) orelse return error.NotFound; 1295 const uset = try Repo.DB.SortedSet(.read_only).init(ucur); 1296 try std.testing.expectEqual(1, try uset.count()); 1297 } 1298 1299 // delete repo1 -> dense order (no tombstone) 1300 try evt.consume(.xit, repo_opts, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{ 1301 .{ .id = std.fmt.bytesToHex(repo_ids[1], .lower), .timestamp = 200, .event = .{ .repo = null } }, 1302 }); 1303 { 1304 const moment = try evt.currentMoment(repo_opts, &repo); 1305 try Check.order(Repo.DB, repo_opts.hash, moment, &.{ &repo_ids[3], &repo_ids[2], &repo_ids[0] }); 1306 } 1307 1308 // update repo0 at a later timestamp -> keeps its original place 1309 try evt.consume(.xit, repo_opts, io, allocator, &repo, evt.events_ref, &[_]evt.EventWithId{ 1310 .{ .id = std.fmt.bytesToHex(repo_ids[0], .lower), .timestamp = 300, .event = .{ .repo = .{ .user_id = &user_id, .name = "repo0", .description = "updated" } } }, 1311 }); 1312 { 1313 const moment = try evt.currentMoment(repo_opts, &repo); 1314 try Check.order(Repo.DB, repo_opts.hash, moment, &.{ &repo_ids[3], &repo_ids[2], &repo_ids[0] }); 1315 1316 // the value really was updated 1317 const e2r_cur = try moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->repo")) orelse return error.NotFound; 1318 const e2r = try Repo.DB.HashMap(.read_only).init(e2r_cur); 1319 const rc = try e2r.getCursor(hash.hashInt(repo_opts.hash, &repo_ids[0])) orelse return error.NotFound; 1320 const rm = try Repo.DB.HashMap(.read_only).init(rc); 1321 const re = try evt.read(evt.Repo, Repo.DB, repo_opts.hash, &arena, rm); 1322 try std.testing.expectEqualStrings("updated", re.description); 1323 } 1324 } 1325