╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ notify.zig ───────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
21 22 fn readMode(io: std.Io, allocator: std.mem.Allocator, arena: *std.heap.ArenaAllocator, users_dir: []const u8, user_id: *const [evt.event_id_size]u8, repo_id: *const [evt.event_id_size]u8, thread_id: *const [evt.event_id_size]u8) !?evt.Notify.Mode { 23 var user_repo = (try evt.openUserRepo(io, allocator, users_dir, user_id)) orelse return error.TestUnexpectedResult; 24 defer user_repo.deinit(io, allocator); 25 const moment = (try evt.userMoment(&user_repo)) orelse return error.TestUnexpectedResult; 26 return try evt.Notify.readMode(evt.UserDB, evt.user_repo_opts.hash, moment, arena, repo_id, thread_id); 27 } 28 29 fn readSubscribers(io: std.Io, allocator: std.mem.Allocator, arena: *std.heap.ArenaAllocator, users_dir: []const u8, owner_id: *const [evt.event_id_size]u8, notif_id: *const [evt.event_id_size]u8) ![]const [evt.event_id_size]u8 { 30 var user_repo = (try evt.openUserRepo(io, allocator, users_dir, owner_id)) orelse return error.TestUnexpectedResult; 31 defer user_repo.deinit(io, allocator); 32 return try notify.readSubscribers(try user_repo.core.latestMoment(), arena, notif_id); 33 } 34 35 test "notifs" { 36 const io = std.testing.io; 37 const allocator = std.testing.allocator; 38 39 var temp = std.testing.tmpDir(.{}); 40 defer temp.cleanup(); 41 const temp_path = try temp.dir.realPathFileAlloc(io, ".", allocator); 42 defer allocator.free(temp_path); 43 const users_dir = try std.Io.Dir.path.join(allocator, &.{ temp_path, "users" }); 44 defer allocator.free(users_dir); 45 46 var arena = std.heap.ArenaAllocator.init(allocator); 47 defer arena.deinit(); 48 49 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); 50 const owner_id = evt.EventWithId.randomId(prng.random()); 51 const recipient_id = evt.EventWithId.randomId(prng.random()); 52 for ([_]*const [evt.event_id_size]u8{ &owner_id, &recipient_id }) |user_id| { 53 const path = try evt.userRepoPath(allocator, users_dir, user_id); 54 defer allocator.free(path); 55 var user_repo = try evt.initUserRepo(io, allocator, path); 56 defer user_repo.deinit(io, allocator); 57 } 58 const location = try evt.createRepo(io, allocator, users_dir, &owner_id, author, "repo", "", .private, repo_opts.hash); 59 60 // 61 // notifs 62 // 63 64 const thread_a = evt.EventWithId.randomId(prng.random()); 65 const thread_b = evt.EventWithId.randomId(prng.random()); 66 var child_ids: [4][evt.event_id_size]u8 = undefined; 67 for (&child_ids) |*id| id.* = evt.EventWithId.randomId(prng.random()); 68 const notif_a: notify.Notif = .{ 69 .repo_id = location.repo_id, 70 .owner_name = "owner", 71 .repo_name = "repo", 72 .thread_id = thread_a, 73 .thread_kind = .issue, 74 .title = "a", 75 .last_child_id = child_ids[0], 76 }; 77 var notif_b = notif_a; 78 notif_b.thread_id = thread_b; 79 notif_b.title = "b"; 80 notif_b.last_child_id = child_ids[1]; 81 const id_a = notify.idOf(&location.repo_id, &thread_a); 82 83 try notify.deliver(io, allocator, users_dir, &recipient_id, &.{ notif_a, notif_b }); 84 try std.testing.expectEqual(2, try unreadCount(io, allocator, users_dir, &recipient_id)); 85 try std.testing.expectEqual(2, (try readRecent(io, allocator, &arena, users_dir, &recipient_id)).len); 86 87 // a bump updates the thread's one notif. times tie within a second, so order isn't checked. 88 var bump = notif_a; 89 bump.last_child_id = child_ids[2]; 90 try notify.deliver(io, allocator, users_dir, &recipient_id, &.{bump}); 91 bump.last_child_id = child_ids[3]; 92 try notify.deliver(io, allocator, users_dir, &recipient_id, &.{bump}); 93 { 94 const recent = try readRecent(io, allocator, &arena, users_dir, &recipient_id); 95 try std.testing.expectEqual(2, recent.len); 96 for (recent) |item| { 97 if (std.mem.eql(u8, &item.notif.thread_id, &thread_a)) try std.testing.expectEqualSlices(u8, &child_ids[3], &item.notif.last_child_id); 98 } 99 } 100 101 // a read of a child the thread has moved past leaves it unread 102 try notify.markRead(io, allocator, users_dir, &recipient_id, &id_a, &child_ids[2]); 103 try std.testing.expectEqual(2, try unreadCount(io, allocator, users_dir, &recipient_id)); 104 try notify.markRead(io, allocator, users_dir, &recipient_id, &id_a, &child_ids[3]); 105 try std.testing.expectEqual(1, try unreadCount(io, allocator, users_dir, &recipient_id)); 106 for (try readRecent(io, allocator, &arena, users_dir, &recipient_id)) |item| { 107 try std.testing.expectEqual(!std.mem.eql(u8, &item.notif.thread_id, &thread_a), item.unread); 108 } 109 110 try notify.dismiss(io, allocator, users_dir, &recipient_id, &id_a); 111 { 112 const recent = try readRecent(io, allocator, &arena, users_dir, &recipient_id); 113 try std.testing.expectEqual(1, recent.len); 114 try std.testing.expectEqualSlices(u8, &thread_b, &recent[0].notif.thread_id); 115 } 116 117 // 118 // notify records 119 // 120 121 // muting a thread drops its notif 122 try notify.mute(io, allocator, users_dir, &recipient_id, location, &thread_b, author); 123 try std.testing.expectEqual(0, (try readRecent(io, allocator, &arena, users_dir, &recipient_id)).len); 124 125 // subscribing replaces the mute record and indexes the subscriber in the owner's repo 126 const id_b = notify.idOf(&location.repo_id, &thread_b); 127 try std.testing.expectEqual(.mute, try readMode(io, allocator, &arena, users_dir, &recipient_id, &location.repo_id, &thread_b)); 128 try notify.subscribe(io, allocator, users_dir, &recipient_id, location, &thread_b, author); 129 try std.testing.expectEqual(.subscribe, try readMode(io, allocator, &arena, users_dir, &recipient_id, &location.repo_id, &thread_b)); 130 try notify.deliver(io, allocator, users_dir, &recipient_id, &.{notif_b}); 131 try std.testing.expectEqual(1, (try readRecent(io, allocator, &arena, users_dir, &recipient_id)).len); 132 { 133 const subscribers = try readSubscribers(io, allocator, &arena, users_dir, &owner_id, &id_b); 134 try std.testing.expectEqual(1, subscribers.len); 135 try std.testing.expectEqualSlices(u8, &recipient_id, &subscribers[0]); 136 } 137 138 // muting drops the subscriber from the index 139 try notify.mute(io, allocator, users_dir, &recipient_id, location, &thread_b, author); 140 try std.testing.expectEqual(0, (try readSubscribers(io, allocator, &arena, users_dir, &owner_id, &id_b)).len); 141 142 // past the cap, the oldest go. times tie within a second, so which one goes isn't checked. 143 { 144 const many = try allocator.alloc(notify.Notif, notify.max_notifs + 1); 145 defer allocator.free(many); 146 for (many, 0..) |*notif, i| { 147 notif.* = notif_a; 148 notif.thread_id = @splat(0); 149 std.mem.writeInt(u64, notif.thread_id[0..@sizeOf(u64)], i, .big); 150 } 151 try notify.deliver(io, allocator, users_dir, &recipient_id, many); 152 } 153 try std.testing.expectEqual(notify.max_notifs, (try readRecent(io, allocator, &arena, users_dir, &recipient_id)).len); 154 } 155