Get integration tests working
This commit is contained in:
parent
b79be4bfd1
commit
23da0c6857
5 changed files with 113 additions and 39 deletions
|
@ -69,8 +69,10 @@ pub fn build(b: *std.build.Builder) void {
|
||||||
api_integration.addPackage(util_pkg);
|
api_integration.addPackage(util_pkg);
|
||||||
api_integration.addPackage(http_pkg);
|
api_integration.addPackage(http_pkg);
|
||||||
api_integration.addPackage(main_pkg);
|
api_integration.addPackage(main_pkg);
|
||||||
|
api_integration.addPackage(api_pkg);
|
||||||
api_integration.linkLibC();
|
api_integration.linkLibC();
|
||||||
api_integration.linkSystemLibrary("sqlite3");
|
api_integration.linkSystemLibrary("sqlite3");
|
||||||
|
api_integration.linkSystemLibrary("pq");
|
||||||
|
|
||||||
const integration_tests = b.step("integration-tests", "run tests");
|
const integration_tests = b.step("integration-tests", "run tests");
|
||||||
integration_tests.dependOn(&api_integration.step);
|
integration_tests.dependOn(&api_integration.step);
|
||||||
|
|
|
@ -49,9 +49,7 @@ pub const UsernameValidationError = error{
|
||||||
/// Usernames must satisfy:
|
/// Usernames must satisfy:
|
||||||
/// - Be at least 1 character
|
/// - Be at least 1 character
|
||||||
/// - Be no more than 32 characters
|
/// - Be no more than 32 characters
|
||||||
/// - All characters are in [A-Za-z0-9_.]
|
/// - 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)
|
|
||||||
pub fn validateUsername(username: []const u8) UsernameValidationError!void {
|
pub fn validateUsername(username: []const u8) UsernameValidationError!void {
|
||||||
if (username.len == 0) return error.UsernameEmpty;
|
if (username.len == 0) return error.UsernameEmpty;
|
||||||
if (username.len > max_username_chars) return error.UsernameTooLong;
|
if (username.len > max_username_chars) return error.UsernameTooLong;
|
||||||
|
|
|
@ -6,13 +6,7 @@ const sql = @import("sql");
|
||||||
const Uuid = util.Uuid;
|
const Uuid = util.Uuid;
|
||||||
const DateTime = util.DateTime;
|
const DateTime = util.DateTime;
|
||||||
|
|
||||||
pub const Scheme = enum {
|
pub const Community = struct {
|
||||||
https,
|
|
||||||
http,
|
|
||||||
|
|
||||||
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Kind = enum {
|
pub const Kind = enum {
|
||||||
admin,
|
admin,
|
||||||
local,
|
local,
|
||||||
|
@ -20,7 +14,12 @@ pub const Kind = enum {
|
||||||
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Community = struct {
|
pub const Scheme = enum {
|
||||||
|
https,
|
||||||
|
http,
|
||||||
|
|
||||||
|
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
||||||
|
};
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
|
|
||||||
owner_id: ?Uuid,
|
owner_id: ?Uuid,
|
||||||
|
@ -34,7 +33,7 @@ pub const Community = struct {
|
||||||
|
|
||||||
pub const CreateOptions = struct {
|
pub const CreateOptions = struct {
|
||||||
name: ?[]const u8 = null,
|
name: ?[]const u8 = null,
|
||||||
kind: Kind = .local,
|
kind: Community.Kind = .local,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CreateError = error{
|
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 {
|
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_len = std.mem.indexOfScalar(u8, origin, ':') orelse return error.InvalidOrigin;
|
||||||
const scheme_str = origin[0..scheme_len];
|
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}"
|
// host must be in the format "{scheme}://{host}"
|
||||||
if (origin.len <= scheme_len + ("://").len or
|
if (origin.len <= scheme_len + ("://").len or
|
||||||
|
|
|
@ -5,7 +5,7 @@ const http = @import("http");
|
||||||
const util = @import("util");
|
const util = @import("util");
|
||||||
|
|
||||||
const api = @import("api");
|
const api = @import("api");
|
||||||
const migrations = @import("./migrations.zig");
|
pub const migrations = @import("./migrations.zig"); // TODO
|
||||||
const c = @import("./controllers.zig");
|
const c = @import("./controllers.zig");
|
||||||
|
|
||||||
pub const RequestServer = struct {
|
pub const RequestServer = struct {
|
||||||
|
|
|
@ -1,45 +1,120 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const main = @import("main");
|
const api = @import("api");
|
||||||
|
const migrations = @import("main").migrations;
|
||||||
const sql = @import("sql");
|
const sql = @import("sql");
|
||||||
|
const util = @import("util");
|
||||||
|
|
||||||
const test_config = .{
|
const test_config = .{
|
||||||
.db = .{
|
.db = .{
|
||||||
.sqlite = .{
|
.sqlite = .{
|
||||||
.db_file = ":memory:",
|
.sqlite_file_path = ":memory:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ApiSource = main.api.ApiSource;
|
const ApiSource = api.ApiSource;
|
||||||
const root_user = "root";
|
const root_user = "root";
|
||||||
const root_password = "password1234";
|
const root_password = "password1234";
|
||||||
const admin_host = "example.com";
|
const admin_host = "example.com";
|
||||||
const admin_origin = "https://" ++ admin_host;
|
const admin_origin = "https://" ++ admin_host;
|
||||||
const random_seed = 1234;
|
|
||||||
|
|
||||||
fn makeDb(alloc: std.mem.Allocator) sql.Db {
|
fn makeDb(alloc: std.mem.Allocator) !sql.Conn {
|
||||||
var db = try sql.Db.open(test_config.db);
|
try util.seedThreadPrng();
|
||||||
try main.migrations.up(&db);
|
var db = try sql.Conn.open(test_config.db);
|
||||||
try main.api.setupAdmin(&db, admin_origin, root_user, root_password, alloc);
|
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 {
|
fn connectAndLogin(
|
||||||
main.api.initThreadPrng(random_seed);
|
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 try util.deepClone(alloc, try conn.login(username, password));
|
||||||
return source;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "login as root" {
|
test "login as root" {
|
||||||
const alloc = std.testing.allocator;
|
const alloc = std.testing.allocator;
|
||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
defer arena.deinit();
|
|
||||||
var db = try makeDb(alloc);
|
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});
|
const login = try connectAndLogin(&src, admin_host, root_user, root_password, alloc);
|
||||||
var api = try src.connectUnauthorized(admin_host, arena.allocator());
|
defer util.deepFree(alloc, login);
|
||||||
defer api.close();
|
|
||||||
|
|
||||||
_ = 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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue