From 23da0c6857c7bf560c45b4fdc0ae946245c4159f Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 11 Oct 2022 22:48:08 -0700 Subject: [PATCH] Get integration tests working --- build.zig | 2 + src/api/services/actors.zig | 4 +- src/api/services/communities.zig | 31 ++++----- src/main/main.zig | 2 +- tests/api_integration/lib.zig | 113 +++++++++++++++++++++++++------ 5 files changed, 113 insertions(+), 39 deletions(-) diff --git a/build.zig b/build.zig index 11fb9c8..108f884 100644 --- a/build.zig +++ b/build.zig @@ -69,8 +69,10 @@ pub fn build(b: *std.build.Builder) void { api_integration.addPackage(util_pkg); api_integration.addPackage(http_pkg); api_integration.addPackage(main_pkg); + api_integration.addPackage(api_pkg); api_integration.linkLibC(); api_integration.linkSystemLibrary("sqlite3"); + api_integration.linkSystemLibrary("pq"); const integration_tests = b.step("integration-tests", "run tests"); integration_tests.dependOn(&api_integration.step); diff --git a/src/api/services/actors.zig b/src/api/services/actors.zig index 2c18254..ee7c38d 100644 --- a/src/api/services/actors.zig +++ b/src/api/services/actors.zig @@ -49,9 +49,7 @@ pub const UsernameValidationError = error{ /// Usernames must satisfy: /// - Be at least 1 character /// - Be no more than 32 characters -/// - All characters are in [A-Za-z0-9_.] -/// Note that the '.' character is not allowed in all usernames, and -/// is intended for use in federated instance actors (as many instances do) +/// - All characters are in [A-Za-z0-9_] pub fn validateUsername(username: []const u8) UsernameValidationError!void { if (username.len == 0) return error.UsernameEmpty; if (username.len > max_username_chars) return error.UsernameTooLong; diff --git a/src/api/services/communities.zig b/src/api/services/communities.zig index 7c2a2fa..84f07b6 100644 --- a/src/api/services/communities.zig +++ b/src/api/services/communities.zig @@ -6,21 +6,20 @@ const sql = @import("sql"); const Uuid = util.Uuid; const DateTime = util.DateTime; -pub const Scheme = enum { - https, - http, - - pub const jsonStringify = util.jsonSerializeEnumAsString; -}; - -pub const Kind = enum { - admin, - local, - - pub const jsonStringify = util.jsonSerializeEnumAsString; -}; - pub const Community = struct { + pub const Kind = enum { + admin, + local, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; + + pub const Scheme = enum { + https, + http, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; id: Uuid, owner_id: ?Uuid, @@ -34,7 +33,7 @@ pub const Community = struct { pub const CreateOptions = struct { name: ?[]const u8 = null, - kind: Kind = .local, + kind: Community.Kind = .local, }; pub const CreateError = error{ @@ -47,7 +46,7 @@ pub const CreateError = error{ pub fn create(db: anytype, origin: []const u8, options: CreateOptions, alloc: std.mem.Allocator) CreateError!Uuid { const scheme_len = std.mem.indexOfScalar(u8, origin, ':') orelse return error.InvalidOrigin; const scheme_str = origin[0..scheme_len]; - const scheme = std.meta.stringToEnum(Scheme, scheme_str) orelse return error.UnsupportedScheme; + const scheme = std.meta.stringToEnum(Community.Scheme, scheme_str) orelse return error.UnsupportedScheme; // host must be in the format "{scheme}://{host}" if (origin.len <= scheme_len + ("://").len or diff --git a/src/main/main.zig b/src/main/main.zig index dc96f3c..f722294 100644 --- a/src/main/main.zig +++ b/src/main/main.zig @@ -5,7 +5,7 @@ const http = @import("http"); const util = @import("util"); const api = @import("api"); -const migrations = @import("./migrations.zig"); +pub const migrations = @import("./migrations.zig"); // TODO const c = @import("./controllers.zig"); pub const RequestServer = struct { diff --git a/tests/api_integration/lib.zig b/tests/api_integration/lib.zig index c20ef70..1027043 100644 --- a/tests/api_integration/lib.zig +++ b/tests/api_integration/lib.zig @@ -1,45 +1,120 @@ const std = @import("std"); -const main = @import("main"); +const api = @import("api"); +const migrations = @import("main").migrations; const sql = @import("sql"); +const util = @import("util"); const test_config = .{ .db = .{ .sqlite = .{ - .db_file = ":memory:", + .sqlite_file_path = ":memory:", }, }, }; -const ApiSource = main.api.ApiSource; +const ApiSource = api.ApiSource; const root_user = "root"; const root_password = "password1234"; const admin_host = "example.com"; const admin_origin = "https://" ++ admin_host; -const random_seed = 1234; -fn makeDb(alloc: std.mem.Allocator) sql.Db { - var db = try sql.Db.open(test_config.db); - try main.migrations.up(&db); - try main.api.setupAdmin(&db, admin_origin, root_user, root_password, alloc); +fn makeDb(alloc: std.mem.Allocator) !sql.Conn { + try util.seedThreadPrng(); + var db = try sql.Conn.open(test_config.db); + try migrations.up(try db.acquire()); + try api.setupAdmin(try db.acquire(), admin_origin, root_user, root_password, alloc); + return db; } -fn makeApi(alloc: std.mem.Allocator, db: *sql.Db) !ApiSource { - main.api.initThreadPrng(random_seed); +fn connectAndLogin( + api_source: *api.ApiSource, + host: []const u8, + username: []const u8, + password: []const u8, + alloc: std.mem.Allocator, +) !api.LoginResponse { + var conn = try api_source.connectUnauthorized(host, alloc); + defer conn.close(); - const source = try ApiSource.init(alloc, test_config, db); - return source; + return try util.deepClone(alloc, try conn.login(username, password)); } test "login as root" { const alloc = std.testing.allocator; - var arena = std.heap.ArenaAllocator.init(alloc); - defer arena.deinit(); var db = try makeDb(alloc); - var src = try makeApi(alloc, &db); + var src = try ApiSource.init(&db); - std.debug.print("\npassword: {s}\n", .{root_password}); - var api = try src.connectUnauthorized(admin_host, arena.allocator()); - defer api.close(); + const login = try connectAndLogin(&src, admin_host, root_user, root_password, alloc); + defer util.deepFree(alloc, login); - _ = try api.login("root", root_password); + var conn = try src.connectToken(admin_host, login.token, alloc); + defer conn.close(); + const auth = try conn.verifyAuthorization(); + + try std.testing.expectEqual(login.user_id, auth.id); + try std.testing.expectEqualStrings(root_user, auth.username); + try std.testing.expectEqualStrings(admin_host, auth.host); +} + +test "create community" { + const alloc = std.testing.allocator; + var db = try makeDb(alloc); + var src = try ApiSource.init(&db); + + const login = try connectAndLogin(&src, admin_host, root_user, root_password, alloc); + defer util.deepFree(alloc, login); + + var conn = try src.connectToken(admin_host, login.token, alloc); + defer conn.close(); + + const host = "fedi.example.com"; + const community = try conn.createCommunity("https://" ++ host); + + try std.testing.expectEqual(api.Community.Scheme.https, community.scheme); + try std.testing.expectEqual(api.Community.Kind.local, community.kind); + try std.testing.expect(community.owner_id == null); + try std.testing.expectEqualStrings(host, community.host); + try std.testing.expectEqualStrings(host, community.name); +} + +test "create community and transfer to new owner" { + const alloc = std.testing.allocator; + var db = try makeDb(alloc); + var src = try ApiSource.init(&db); + + const root_login = try connectAndLogin(&src, admin_host, root_user, root_password, alloc); + defer util.deepFree(alloc, root_login); + + const host = "fedi.example.com"; + const invite = blk: { + var conn = try src.connectToken(admin_host, root_login.token, alloc); + defer conn.close(); + + const community = try conn.createCommunity("https://" ++ host); + const invite = try conn.createInvite(.{ .to_community = community.id, .kind = .community_owner }); + break :blk try util.deepClone(alloc, invite); + }; + defer util.deepFree(alloc, invite); + + const username = "testuser"; + const password = root_password; + { + var conn = try src.connectUnauthorized(host, alloc); + defer conn.close(); + + _ = try conn.register(username, password, .{ .invite_code = invite.code }); + } + + const login = try connectAndLogin(&src, host, username, password, alloc); + defer util.deepFree(alloc, login); + + var conn = try src.connectToken(host, login.token, alloc); + defer conn.close(); + + const auth = try conn.verifyAuthorization(); + + try std.testing.expectEqual(login.user_id, auth.id); + try std.testing.expectEqualStrings(username, auth.username); + try std.testing.expectEqualStrings(host, auth.host); + try std.testing.expectEqual(invite.community_id, auth.community_id); }