feat: continue work on server

This commit is contained in:
dualfroz committed 2026-09-11 19:14:32 +02:00
1 parent 2a7fee541b
commit 449a952c3e
1 file changed
+157
+157
View File
@@ -3284,3 +3284,160 @@ test "server table_export renders a real table to csv and json over TCP, matchin
try std.testing.expect(std.mem.indexOf(u8, jsonResult.content, "krakow") != null); try std.testing.expect(std.mem.indexOf(u8, jsonResult.content, "krakow") != null);
} }
test "server table_import loads csv and json content sent over TCP into a real table" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = try tmp.dir.realpathAlloc(allocator, ".");
defer allocator.free(path);
var db = try testDb(allocator, path);
defer db.close();
const address = try std.net.Address.parseIp("127.0.0.1", 0);
var server = try Server.init(allocator, &db, address);
try server.start();
defer server.stop();
const client_mod = @import("client.zig");
var client = try client_mod.Client.connect(allocator, server.boundAddress());
defer client.close();
const csvImported = try client.tableImport("from_csv", .csv, "__pk,city\ni:1,s:warsaw\ni:2,s:krakow\n");
try std.testing.expectEqual(@as(u64, 2), csvImported);
const jsonImported = try client.tableImport("from_json", .json, "[{\"__pk\": \"i:1\", \"city\": \"s:gdansk\"}]");
try std.testing.expectEqual(@as(u64, 1), jsonImported);
const csvRows = try client.tableSelect(allocator, "from_csv", "", 0, 0, &.{});
defer protocol.freeRows(allocator, csvRows);
try std.testing.expectEqual(@as(usize, 2), csvRows.len);
const jsonRows = try client.tableSelect(allocator, "from_json", "city eq s:gdansk", 0, 0, &.{});
defer protocol.freeRows(allocator, jsonRows);
try std.testing.expectEqual(@as(usize, 1), jsonRows.len);
}
test "server table_export then table_import round trips a real table over two TCP round trips" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = try tmp.dir.realpathAlloc(allocator, ".");
defer allocator.free(path);
var db = try testDb(allocator, path);
defer db.close();
{
var table = try querymod.Table.open(&db, allocator, "source");
defer table.deinit();
try table.put(.{ .int = 1 }, &.{ .{ .name = "city", .value = .{ .text = "warsaw" } }, .{ .name = "score", .value = .{ .float = 4.5 } } });
try table.put(.{ .int = 2 }, &.{.{ .name = "city", .value = .{ .text = "lodz" } }});
try table.put(.{ .int = 3 }, &.{});
}
const address = try std.net.Address.parseIp("127.0.0.1", 0);
var server = try Server.init(allocator, &db, address);
try server.start();
defer server.stop();
const client_mod = @import("client.zig");
var client = try client_mod.Client.connect(allocator, server.boundAddress());
defer client.close();
var exported = try client.tableExport(allocator, "source", .json);
defer exported.deinit(allocator);
try std.testing.expectEqual(@as(u64, 3), exported.rowCount);
const imported = try client.tableImport("dest", .json, exported.content);
try std.testing.expectEqual(@as(u64, 3), imported);
const warsawRows = try client.tableSelect(allocator, "dest", "city eq s:warsaw", 0, 0, &.{});
defer protocol.freeRows(allocator, warsawRows);
try std.testing.expectEqual(@as(usize, 1), warsawRows.len);
try std.testing.expectEqual(@as(f64, 4.5), warsawRows[0].get("score").?.float);
const untouched = try client.tableSelect(allocator, "dest", "city eq s:lodz", 0, 0, &.{});
defer protocol.freeRows(allocator, untouched);
try std.testing.expectEqual(@as(usize, 1), untouched.len);
const allRows = try client.tableSelect(allocator, "dest", "", 0, 0, &.{});
defer protocol.freeRows(allocator, allRows);
try std.testing.expectEqual(@as(usize, 3), allRows.len);
}
test "server table_export succeeds on a never-written table (schemaless tables have no declare step), table_import rejects malformed content without crashing" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = try tmp.dir.realpathAlloc(allocator, ".");
defer allocator.free(path);
var db = try testDb(allocator, path);
defer db.close();
const address = try std.net.Address.parseIp("127.0.0.1", 0);
var server = try Server.init(allocator, &db, address);
try server.start();
defer server.stop();
const client_mod = @import("client.zig");
var client = try client_mod.Client.connect(allocator, server.boundAddress());
defer client.close();
// `table` is just a keyspace prefix, never declared ahead of time (see
// `query.Table.open`), so exporting one nothing has ever put a row into
// is a valid zero-row export, not an error.
var exported = try client.tableExport(allocator, "nosuch", .csv);
defer exported.deinit(allocator);
try std.testing.expectEqual(@as(u64, 0), exported.rowCount);
const badImport = client.tableImport("nosuch", .json, "not json");
try std.testing.expectError(client_mod.ClientError.ServerInternal, badImport);
// The connection survives an internal error and can still serve a
// good request right after.
const rowCount = try client.tableImport("nosuch", .json, "[]");
try std.testing.expectEqual(@as(u64, 0), rowCount);
}
test "server rejects a malformed table_export frame (bad format byte) with bad_request without crashing" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = try tmp.dir.realpathAlloc(allocator, ".");
defer allocator.free(path);
var db = try testDb(allocator, path);
defer db.close();
{
var table = try querymod.Table.open(&db, allocator, "people");
defer table.deinit();
try table.put(.{ .int = 1 }, &.{});
}
const address = try std.net.Address.parseIp("127.0.0.1", 0);
var server = try Server.init(allocator, &db, address);
try server.start();
defer server.stop();
const stream = try std.net.tcpConnectToAddress(server.boundAddress());
defer stream.close();
// A well-formed table name ("people") followed by a format byte
// outside the DumpFormat enum (99) - `decodeTableExport` itself must
// reject this before `Table.open` is ever reached.
var payload = std.ArrayList(u8).init(allocator);
defer payload.deinit();
var lenBuf: [4]u8 = undefined;
std.mem.writeInt(u32, &lenBuf, 6, .little);
try payload.appendSlice(&lenBuf);
try payload.appendSlice("people");
try payload.append(99);
try protocol.writeFrame(stream.writer(), @intFromEnum(protocol.Op.table_export), payload.items);
var frame = try protocol.readFrame(allocator, stream.reader());
defer frame.deinit();
try std.testing.expectEqual(@intFromEnum(protocol.Status.bad_request), frame.tag);