// SPDX-FileCopyrightText: 2026 Romain Maneschi // // SPDX-License-Identifier: MIT const std = @import("std"); const Allocator = std.mem.Allocator; const fs = @import("src/fs.zig"); const model = @import("src/model/index.zig"); pub const FakeServerError = error{ NotForMe, UnknownFsBase, }; pub const FakeServer = struct { io: std.Io, allocator: Allocator, dir_worktree: std.Io.Dir, dir_webcontent: std.Io.Dir, dir_cache: std.Io.Dir, // grfs_git: *model.GrFs, // TODO // grfs_web: *model.GrFs, // grfs_cache: *model.GrFs, override: Override = .{}, nb_call: NbCall = .{}, pub const Override = struct { forge_conf: ?*const fn () anyerror!ForgeConf = null, copy_file: ?*const fn (from_fs: fs.FsBase, from_path: []const u8, to_fs: fs.FsBase, to_path: []const u8) anyerror!void = null, delete_file: ?*const fn (from_fs: fs.FsBase, filename: []const u8) anyerror!void = null, write_content: ?*const fn (from_fs: fs.FsBase, path: []const u8, content: []const u8) anyerror!void = null, // TODO }; pub const NbCall = struct { forge_conf: u32 = 0, worktree: u32 = 0, webcontent: u32 = 0, cache: u32 = 0, copy_file: u32 = 0, delete_file: u32 = 0, move_file: u32 = 0, replace_content: u32 = 0, write_content: u32 = 0, log: u32 = 0, log_error: u32 = 0, }; pub const ForgeConf = struct { domain: []const u8, external_ssh_addr: []const u8, external_http_addr: []const u8, root_repository_name: []const u8, }; pub fn init(base_allocator: Allocator, io: std.Io) !*FakeServer { var arena = std.heap.ArenaAllocator.init(base_allocator); defer arena.deinit(); const self = try base_allocator.create(FakeServer); const rng_impl: std.Random.IoSource = .{ .io = io }; const secureRand = rng_impl.interface(); const rand_id = secureRand.int(u32); const alloc = arena.allocator(); const cwd = std.Io.Dir; const path = "/tmp/gitroot_test"; cwd.createDirAbsolute(io, path, .default_dir) catch undefined; const path2 = try std.fmt.allocPrint(alloc, "/tmp/gitroot_test/{d}", .{rand_id}); try cwd.createDirAbsolute(io, path2, .default_dir); const dir_all = try cwd.openDirAbsolute(io, path, .{}); const dir_worktree = try dir_all.createDirPathOpen(io, "worktree", .{}); const dir_webcontent = try dir_all.createDirPathOpen(io, "webcontent", .{}); const dir_cache = try dir_all.createDirPathOpen(io, "cache", .{}); self.* = .{ .io = io, .allocator = base_allocator, .dir_worktree = dir_worktree, .dir_webcontent = dir_webcontent, .dir_cache = dir_cache, }; return self; } pub fn deinit(self: *FakeServer) void { const base_allocator = self.allocator; base_allocator.destroy(self); } pub fn setOverride(self: *FakeServer, o: Override) *FakeServer { self.override = o; return self; } pub fn forgeConf(self: *FakeServer) anyerror!ForgeConf { self.nb_call.forge_conf += 1; if (self.override.forge_conf) |mock| { return mock(); } return ForgeConf{ .domain = "test", .external_ssh_addr = "ssh://127.0.0.1:4545/", .external_http_addr = "http://127.0.0.1:4546/", .root_repository_name = "root", }; } fn getDir(self: *FakeServer, from_fs: fs.FsBase) std.Io.Dir { const dir = switch (from_fs) { .WORKTREE => self.dir_worktree, .WEBCONTENT => self.dir_webcontent, .CACHE => self.dir_cache, }; return dir; } pub fn writeContent(self: *FakeServer, from_fs: fs.FsBase, path: []const u8, content: []const u8) anyerror!void { self.nb_call.write_content += 1; if (self.override.write_content) |mock| { mock(from_fs, path, content) catch |err| { if (err != FakeServerError.NotForMe) return err; }; } const dir = self.getDir(from_fs); const file = try dir.createFile(self.io, path, .{}); defer file.close(); try file.writeAll(content); } pub fn copyFile(self: *FakeServer, from_fs: fs.FsBase, from_path: []const u8, to_fs: fs.FsBase, to_path: []const u8) anyerror!void { self.nb_call.copy_file += 1; if (self.override.copy_file) |mock| { mock(from_fs, from_path, to_fs, to_path) catch |err| { if (err != FakeServerError.NotForMe) return err; }; } // TODO return; } pub fn deleteFile(self: *FakeServer, from_fs: fs.FsBase, filename: []const u8) anyerror!void { self.nb_call.delete_file += 1; if (self.override.delete_file) |mock| { mock(from_fs, filename) catch |err| { if (err != FakeServerError.NotForMe) return err; }; } const dir = try self.getDir(from_fs); try dir.deleteFile(filename); } pub fn moveFile(self: *FakeServer, from_fs: fs.FsBase, from_path: []const u8, to_fs: fs.FsBase, to_path: []const u8) anyerror!void { self.nb_call.move_file += 1; try self.copyFile(from_fs, from_path, to_fs, to_path); try self.deleteFile(from_fs, from_path); } pub fn replaceContent(self: *FakeServer, from_fs: fs.FsBase, path: []const u8, old_content: []const u8, content: []const u8) anyerror!void { self.nb_call.replace_content += 1; var arena = std.heap.ArenaAllocator.init(self.base_allocator); defer arena.deinit(); const abs_path = try self.getAbsoluteFsPath(from_fs, path); const file = try std.fs.cwd().openFile(abs_path, .{}); defer file.close(); const size = (try file.stat()).size; const buf = try arena.allocator().alloc(u8, size); const read = try file.readAll(buf); const index = std.mem.indexOf(u8, buf[0..read], old_content) orelse return; const new_buf = try std.mem.concat(arena.allocator(), u8, &.{ buf[0..index], content, buf[index + old_content.len .. read], }); try self.writeContent(from_fs, path, new_buf); } pub fn log(self: *FakeServer, message: []const u8) void { self.nb_call.log += 1; std.debug.print("[INFO] {s}\n", .{message}); } pub fn logError(self: *FakeServer, message: []const u8, err: anytype) void { self.nb_call.log_error += 1; std.debug.print("[ERROR] {s} : {}\n", .{ message, err }); } };