diff --git a/build.zig b/build.zig index a650002..d0487bf 100644 --- a/build.zig +++ b/build.zig @@ -17,6 +17,12 @@ const sql_pkg = std.build.Pkg{ .dependencies = &.{util_pkg}, }; +const main_pkg = std.build.Pkg{ + .name = "main", + .source = std.build.FileSource.relative("src/main/main.zig"), + .dependencies = &.{ util_pkg, http_pkg, sql_pkg }, +}; + pub fn build(b: *std.build.Builder) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which @@ -50,6 +56,17 @@ pub fn build(b: *std.build.Builder) void { unit_tests.dependOn(&http_tests.step); unit_tests.dependOn(&sql_tests.step); + const api_integration = b.addTest("./tests/api_integration/lib.zig"); + api_integration.addPackage(sql_pkg); + api_integration.addPackage(util_pkg); + api_integration.addPackage(http_pkg); + api_integration.addPackage(main_pkg); + api_integration.linkLibC(); + api_integration.linkSystemLibrary("sqlite3"); + + const integration_tests = b.step("integration-tests", "run tests"); + integration_tests.dependOn(&api_integration.step); + const run_cmd = exe.run(); //run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { diff --git a/src/main/api.zig b/src/main/api.zig index 929042e..6053435 100644 --- a/src/main/api.zig +++ b/src/main/api.zig @@ -101,11 +101,10 @@ pub const ApiSource = struct { pub const Conn = ApiConn(db.Database); const root_username = "root"; - const root_password_envvar = "CLUSTER_ROOT_PASSWORD"; - pub fn init(alloc: std.mem.Allocator, cfg: Config) !ApiSource { + pub fn init(alloc: std.mem.Allocator, cfg: Config, root_password: ?[]const u8) !ApiSource { var self = ApiSource{ - .db = try db.Database.init(), + .db = try db.Database.init(cfg.db.sqlite.db_file), .internal_alloc = alloc, .config = cfg, }; @@ -113,15 +112,13 @@ pub const ApiSource = struct { if ((try services.users.lookupByUsername(&self.db, root_username, null)) == null) { std.log.info("No cluster root user detected. Creating...", .{}); - const root_password = std.os.getenv(root_password_envvar) orelse { - std.log.err( - "No root user created and no password specified. Please provide the password for the root user by the ${s} environment variable for initial startup. This only needs to be done once", - .{root_password_envvar}, - ); - @panic("No root password provided"); - }; - - _ = try services.users.create(&self.db, root_username, root_password, null, .{}, alloc); + // TODO: Fix this + const password = root_password orelse return error.NeedRootPassword; + std.debug.print("\npassword: {s}\n", .{password}); + var arena = std.heap.ArenaAllocator.init(alloc); + defer arena.deinit(); + const user_id = try services.users.create(&self.db, root_username, password, null, .{}, arena.allocator()); + std.debug.print("Created {s} ID {}", .{ root_username, user_id }); } return self; diff --git a/src/main/db.zig b/src/main/db.zig index fa7aa46..1e24d30 100644 --- a/src/main/db.zig +++ b/src/main/db.zig @@ -131,8 +131,8 @@ pub const ExecError = sql.PrepareError || sql.RowGetError || sql.BindError || st pub const Database = struct { db: sql.Sqlite, - pub fn init() !Database { - var db = try sql.Sqlite.open("./test.db"); + pub fn init(file_path: [:0]const u8) !Database { + var db = try sql.Sqlite.open(file_path); errdefer db.close(); try migrations.up(&db); diff --git a/src/main/main.zig b/src/main/main.zig index b93e899..70f628b 100644 --- a/src/main/main.zig +++ b/src/main/main.zig @@ -3,7 +3,7 @@ const builtin = @import("builtin"); const http = @import("http"); const util = @import("util"); -const api = @import("./api.zig"); +pub const api = @import("./api.zig"); const models = @import("./db/models.zig"); const Uuid = util.Uuid; const c = @import("./controllers.zig"); @@ -45,13 +45,13 @@ fn prepare(comptime route_desc: type) Route { pub const RequestServer = struct { alloc: std.mem.Allocator, - api: api.ApiSource, + api: *api.ApiSource, config: Config, - fn init(alloc: std.mem.Allocator, config: Config) !RequestServer { + fn init(alloc: std.mem.Allocator, src: *api.ApiSource, config: Config) !RequestServer { return RequestServer{ .alloc = alloc, - .api = try api.ApiSource.init(alloc, config), + .api = src, .config = config, }; } @@ -82,6 +82,12 @@ pub const RequestServer = struct { pub const Config = struct { cluster_host: []const u8, + db: struct { + sqlite: struct { + db_file: [:0]const u8, + }, + }, + root_password: ?[]const u8 = null, }; fn loadConfig(alloc: std.mem.Allocator) !Config { @@ -95,10 +101,21 @@ fn loadConfig(alloc: std.mem.Allocator) !Config { return std.json.parse(Config, &ts, .{ .allocator = alloc }); } +const root_password_envvar = "CLUSTER_ROOT_PASSWORD"; pub fn main() anyerror!void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const cfg = try loadConfig(gpa.allocator()); - var srv = try RequestServer.init(gpa.allocator(), cfg); + var cfg = try loadConfig(gpa.allocator()); + var api_src = api.ApiSource.init(gpa.allocator(), cfg, std.os.getenv(root_password_envvar)) catch |err| switch (err) { + error.NeedRootPassword => { + std.log.err( + "No root user created and no password specified. Please provide the password for the root user by the ${s} environment variable for initial startup. This only needs to be done once", + .{root_password_envvar}, + ); + return err; + }, + else => return err, + }; + var srv = try RequestServer.init(gpa.allocator(), &api_src, cfg); api.initThreadPrng(@bitCast(u64, std.time.milliTimestamp())); return srv.listenAndRun(std.net.Address.parseIp("0.0.0.0", 8080) catch unreachable); } diff --git a/tests/api_integration/lib.zig b/tests/api_integration/lib.zig new file mode 100644 index 0000000..5b0f3d4 --- /dev/null +++ b/tests/api_integration/lib.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const main = @import("main"); + +const cluster_host = "test_host"; +const test_config = .{ + .cluster_host = cluster_host, + .db = .{ + .sqlite = .{ + .db_file = ":memory:", + }, + }, +}; + +const ApiSource = main.api.ApiSource; +const root_password = "password"; +const random_seed = 1234; + +fn makeApi(alloc: std.mem.Allocator) !ApiSource { + main.api.initThreadPrng(random_seed); + + const source = try ApiSource.init(alloc, test_config, root_password); + return source; +} + +test "login as root" { + const alloc = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(alloc); + defer arena.deinit(); + var src = try makeApi(alloc); + + std.debug.print("\npassword: {s}\n", .{root_password}); + var api = try src.connectUnauthorized(cluster_host, arena.allocator()); + defer api.close(); + + _ = try api.login("root", root_password); +}