diff --git a/src/protocol.zig b/src/protocol.zig index c35bcda..3126eaf 100644 --- a/src/protocol.zig +++ b/src/protocol.zig @@ -3141,3 +3141,157 @@ test "TtlAction.fromByte rejects an unknown action" { try std.testing.expectEqual(TtlAction.sweep, try TtlAction.fromByte(3)); } +test "ttl_op expire payload round trips key and seconds" { + const allocator = std.testing.allocator; + const payload = try encodeTtlOp(allocator, .expire, "session:1", 30); + defer allocator.free(payload); + const args = try decodeTtlOp(payload); + try std.testing.expectEqual(TtlAction.expire, args.action); + try std.testing.expectEqualStrings("session:1", args.key); + try std.testing.expectEqual(@as(u64, 30), args.seconds); +} + +test "ttl_op sweep payload carries an empty key and zero seconds" { + const allocator = std.testing.allocator; + const payload = try encodeTtlOp(allocator, .sweep, "", 0); + defer allocator.free(payload); + const args = try decodeTtlOp(payload); + try std.testing.expectEqual(TtlAction.sweep, args.action); + try std.testing.expectEqualStrings("", args.key); + try std.testing.expectEqual(@as(u64, 0), args.seconds); +} + +test "ttl result round trips noKey, noExpiry, and seconds" { + const allocator = std.testing.allocator; + + const noKeyPayload = try encodeTtlResult(allocator, .noKey); + defer allocator.free(noKeyPayload); + try std.testing.expectEqual(ttlmod.TtlResult.noKey, try decodeTtlResult(noKeyPayload)); + + const noExpiryPayload = try encodeTtlResult(allocator, .noExpiry); + defer allocator.free(noExpiryPayload); + try std.testing.expectEqual(ttlmod.TtlResult.noExpiry, try decodeTtlResult(noExpiryPayload)); + + const secondsPayload = try encodeTtlResult(allocator, .{ .seconds = 42 }); + defer allocator.free(secondsPayload); + const decoded = try decodeTtlResult(secondsPayload); + try std.testing.expectEqual(@as(u64, 42), decoded.seconds); +} + +test "decodeTtlResult rejects an unknown kind byte" { + const allocator = std.testing.allocator; + var buf = std.ArrayList(u8).init(allocator); + defer buf.deinit(); + const w = encode.ByteWriter.init(&buf); + try w.byte(200); + try std.testing.expectError(error.Malformed, decodeTtlResult(buf.items)); +} + +test "ttl sweep count round trips" { + const allocator = std.testing.allocator; + const payload = try encodeTtlSweepCount(allocator, 7); + defer allocator.free(payload); + try std.testing.expectEqual(@as(u64, 7), try decodeTtlSweepCount(payload)); +} + +test "coll_ttl_op expire payload round trips action, kind, name and seconds" { + const allocator = std.testing.allocator; + const payload = try encodeCollTtlOp(allocator, .expire, .hash, "profile", 30); + defer allocator.free(payload); + const args = try decodeCollTtlOp(payload); + try std.testing.expectEqual(TtlAction.expire, args.action); + try std.testing.expectEqual(collmod.Kind.hash, args.kind); + try std.testing.expectEqualStrings("profile", args.name); + try std.testing.expectEqual(@as(u64, 30), args.seconds); +} + +test "coll_ttl_op round trips every kind's tag byte" { + const allocator = std.testing.allocator; + const kinds = [_]collmod.Kind{ .counter, .hash, .set, .list, .zset }; + for (kinds) |k| { + const payload = try encodeCollTtlOp(allocator, .query, k, "n", 0); + defer allocator.free(payload); + const args = try decodeCollTtlOp(payload); + try std.testing.expectEqual(k, args.kind); + } +} + +test "coll_ttl_op sweep payload carries an empty name and zero seconds" { + const allocator = std.testing.allocator; + const payload = try encodeCollTtlOp(allocator, .sweep, .counter, "", 0); + defer allocator.free(payload); + const args = try decodeCollTtlOp(payload); + try std.testing.expectEqual(TtlAction.sweep, args.action); + try std.testing.expectEqualStrings("", args.name); + try std.testing.expectEqual(@as(u64, 0), args.seconds); +} + +test "decodeCollTtlOp rejects an unknown kind byte" { + const allocator = std.testing.allocator; + var buf = std.ArrayList(u8).init(allocator); + defer buf.deinit(); + const w = encode.ByteWriter.init(&buf); + try w.byte(@intFromEnum(TtlAction.query)); + try w.byte('?'); + try w.putU64(0); + try std.testing.expectError(error.Malformed, decodeCollTtlOp(buf.items)); +} + +test "decodeCollTtlOp rejects an unknown action byte before the kind byte is even read" { + const allocator = std.testing.allocator; + var buf = std.ArrayList(u8).init(allocator); + defer buf.deinit(); + const w = encode.ByteWriter.init(&buf); + try w.byte(200); + try w.byte('H'); + try w.putU64(0); + try std.testing.expectError(error.Malformed, decodeCollTtlOp(buf.items)); +} + +test "CollAction.fromByte rejects an unknown action" { + try std.testing.expectError(error.Malformed, CollAction.fromByte(200)); + try std.testing.expectEqual(CollAction.zset_rank, try CollAction.fromByte(29)); +} + +test "CollAction.kind groups every action under its collection kind" { + try std.testing.expectEqual(collmod.Kind.counter, CollAction.counter_incrby.kind()); + try std.testing.expectEqual(collmod.Kind.hash, CollAction.hash_getall.kind()); + try std.testing.expectEqual(collmod.Kind.set, CollAction.set_diff.kind()); + try std.testing.expectEqual(collmod.Kind.list, CollAction.list_range.kind()); + try std.testing.expectEqual(collmod.Kind.zset, CollAction.zset_rangebyscore.kind()); +} + +test "CollAction.isReadOnly separates reads from writes" { + try std.testing.expect(CollAction.counter_get.isReadOnly()); + try std.testing.expect(!CollAction.counter_set.isReadOnly()); + try std.testing.expect(CollAction.zset_rank.isReadOnly()); + try std.testing.expect(!CollAction.zset_add.isReadOnly()); + try std.testing.expect(CollAction.set_inter.isReadOnly()); + try std.testing.expect(!CollAction.list_lpush.isReadOnly()); +} + +test "coll_op payload round trips action, name, and a variable arg list" { + const allocator = std.testing.allocator; + const args = [_][]const u8{ "alice", "10" }; + const payload = try encodeCollOp(allocator, .zset_add, "leaderboard", &args); + defer allocator.free(payload); + const decoded = try decodeCollOp(allocator, payload); + defer allocator.free(decoded.args); + try std.testing.expectEqual(CollAction.zset_add, decoded.action); + try std.testing.expectEqualStrings("leaderboard", decoded.name); + try std.testing.expectEqual(@as(usize, 2), decoded.args.len); + try std.testing.expectEqualStrings("alice", decoded.args[0]); + try std.testing.expectEqualStrings("10", decoded.args[1]); +} + +test "coll_op payload round trips a zero-arg action" { + const allocator = std.testing.allocator; + const payload = try encodeCollOp(allocator, .hash_getall, "profile", &.{}); + defer allocator.free(payload); + const decoded = try decodeCollOp(allocator, payload); + defer allocator.free(decoded.args); + try std.testing.expectEqual(CollAction.hash_getall, decoded.action); + try std.testing.expectEqualStrings("profile", decoded.name); + try std.testing.expectEqual(@as(usize, 0), decoded.args.len); +} +