const std = @import("std"); const RegistryModule = @import("ArgumentRegistry"); const ArgumentRegistry = RegistryModule.ArgumentRegistry; const metadata = @import("metadata"); const ArgumentTypeModule = @import("ArgumentType"); const ArgumentType = ArgumentTypeModule.ArgumentType; const ParsedValue = ArgumentTypeModule.ParsedValue; test "ArgumentRegistry: init and deinit" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); // Registry should be initialized with empty maps try std.testing.expectEqual(@as(usize, 0), registry.arguments.count()); try std.testing.expectEqual(@as(usize, 0), registry.modules_by_arg.count()); try std.testing.expectEqual(@as(usize, 0), registry.registered_types.count()); try std.testing.expectEqual(@as(usize, 0), registry.parsed_values.count()); } test "ArgumentRegistry: deinit cleans up memory" { var registry = ArgumentRegistry.init(std.testing.allocator); // Add some data try registry.registered_types.put("TestType", {}); var list = std.ArrayListUnmanaged([]const u8){}; try list.append(std.testing.allocator, "module1"); try registry.modules_by_arg.put("test-arg", list); // This should not leak registry.deinit(); } test "ArgumentRegistry: isTypeRegistered" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct = struct { value: u32 }; const OtherStruct = struct { other: bool }; try std.testing.expect(!registry.isTypeRegistered(TestStruct)); try std.testing.expect(!registry.isTypeRegistered(OtherStruct)); } test "ArgumentRegistry: markTypeRegistered" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct = struct { value: u32 }; try std.testing.expect(!registry.isTypeRegistered(TestStruct)); try registry.markTypeRegistered(TestStruct); try std.testing.expect(registry.isTypeRegistered(TestStruct)); } test "ArgumentRegistry: markTypeRegistered multiple types" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct1 = struct { value: u32 }; const TestStruct2 = struct { other: bool }; try registry.markTypeRegistered(TestStruct1); try registry.markTypeRegistered(TestStruct2); try std.testing.expect(registry.isTypeRegistered(TestStruct1)); try std.testing.expect(registry.isTypeRegistered(TestStruct2)); } test "ArgumentRegistry: markTypeRegistered idempotent" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct = struct { value: u32 }; try registry.markTypeRegistered(TestStruct); try registry.markTypeRegistered(TestStruct); // Should not error try std.testing.expect(registry.isTypeRegistered(TestStruct)); } test "ArgumentRegistry: isHelpRequested default false" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); try std.testing.expectEqual(false, registry.isHelpRequested()); } test "ArgumentRegistry: isHelpRequested can be set" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); registry.help_requested = true; try std.testing.expectEqual(true, registry.isHelpRequested()); } test "ArgumentRegistry: getArgument with empty registry" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); try std.testing.expectEqual(@as(?*const metadata.ArgumentMetadata, null), registry.getArgument("verbose")); } test "ArgumentRegistry: getArgument after insertion" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const arg_meta = metadata.ArgumentMetadata{ .field_name = "verbose", .arg_name = "verbose", .arg_type = .bool, .help = "Verbose output", }; try registry.arguments.put("verbose", arg_meta); const found = registry.getArgument("verbose"); try std.testing.expect(found != null); try std.testing.expectEqualStrings("verbose", found.?.field_name); try std.testing.expectEqualStrings("Verbose output", found.?.help); } test "ArgumentRegistry: getModulesForArg empty" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); try std.testing.expectEqual(@as(?std.ArrayListUnmanaged([]const u8), null), registry.getModulesForArg("test")); } test "ArgumentRegistry: getModulesForArg with modules" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); var list = std.ArrayListUnmanaged([]const u8){}; try list.append(std.testing.allocator, "module1"); try list.append(std.testing.allocator, "module2"); try registry.modules_by_arg.put("verbose", list); const found = registry.getModulesForArg("verbose"); try std.testing.expect(found != null); try std.testing.expectEqual(@as(usize, 2), found.?.items.len); } test "ArgumentRegistry: getParsedValue empty" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); try std.testing.expectEqual(@as(?ParsedValue, null), registry.getParsedValue("verbose")); } test "ArgumentRegistry: storeParsedValue and retrieve" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const value = ParsedValue{ .bool = true }; try registry.storeParsedValue("verbose", value); const found = registry.getParsedValue("verbose"); try std.testing.expect(found != null); try std.testing.expectEqual(true, found.?.bool); } test "ArgumentRegistry: storeParsedValue multiple values" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); try registry.storeParsedValue("verbose", .{ .bool = true }); try registry.storeParsedValue("count", .{ .u32 = 42 }); const verbose = registry.getParsedValue("verbose"); const count = registry.getParsedValue("count"); try std.testing.expect(verbose != null); try std.testing.expect(count != null); try std.testing.expectEqual(true, verbose.?.bool); try std.testing.expectEqual(@as(u32, 42), count.?.u32); } test "ArgumentRegistry: storeParsedValue overwrites" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); try registry.storeParsedValue("count", .{ .u32 = 10 }); try registry.storeParsedValue("count", .{ .u32 = 20 }); const found = registry.getParsedValue("count"); try std.testing.expectEqual(@as(u32, 20), found.?.u32); } test "ArgumentRegistry: deinit frees parsed string values" { var registry = ArgumentRegistry.init(std.testing.allocator); const str = try std.testing.allocator.dupe(u8, "test string"); const value = ParsedValue{ .string = str }; try registry.storeParsedValue("name", value); // deinit should free the string registry.deinit(); } test "ArgumentRegistry: deinit frees parsed enum values" { var registry = ArgumentRegistry.init(std.testing.allocator); const name = try std.testing.allocator.dupe(u8, "debug"); const value = ParsedValue{ .enum_type = .{ .name = name, .value = 0, }, }; try registry.storeParsedValue("log-level", value); // deinit should free the enum name registry.deinit(); } test "ArgumentRegistry: multiple operations" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct = struct { verbose: bool, count: u32 }; // Mark type as registered try registry.markTypeRegistered(TestStruct); try std.testing.expect(registry.isTypeRegistered(TestStruct)); // Store some metadata const arg_meta = metadata.ArgumentMetadata{ .field_name = "verbose", .arg_name = "verbose", .arg_type = .bool, }; try registry.arguments.put("verbose", arg_meta); // Store a module list var list = std.ArrayListUnmanaged([]const u8){}; try list.append(std.testing.allocator, "TestModule"); try registry.modules_by_arg.put("verbose", list); // Store a parsed value try registry.storeParsedValue("verbose", .{ .bool = true }); // Verify everything try std.testing.expect(registry.getArgument("verbose") != null); try std.testing.expect(registry.getModulesForArg("verbose") != null); try std.testing.expect(registry.getParsedValue("verbose") != null); } // ============================================================================ // Registration Tests // ============================================================================ test "ArgumentRegistry: registerMetadata simple struct" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct = struct { verbose: bool, count: u32, }; try registry.registerMetadata(TestStruct, "TestModule"); // Should have registered both arguments try std.testing.expect(registry.hasArgument("verbose")); try std.testing.expect(registry.hasArgument("count")); try std.testing.expectEqual(@as(usize, 2), registry.argumentCount()); } test "ArgumentRegistry: registerMetadata with short flags" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct = struct { verbose: bool, pub const meta = .{ .verbose = .{ .short = 'v', .help = "Verbose output", }, }; }; try registry.registerMetadata(TestStruct, "TestModule"); // Should have registered both long and short forms try std.testing.expect(registry.hasArgument("verbose")); try std.testing.expect(registry.hasArgument("v")); } test "ArgumentRegistry: registerMetadata with camelCase" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct = struct { outputFile: []const u8, }; try registry.registerMetadata(TestStruct, "TestModule"); // TODO: Field names aren't converted to kebab-case yet, using direct name try std.testing.expect(registry.hasArgument("outputFile")); try std.testing.expect(!registry.hasArgument("output-file")); } test "ArgumentRegistry: registerMetadata skips duplicate type" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct = struct { verbose: bool, }; try registry.registerMetadata(TestStruct, "Module1"); try registry.registerMetadata(TestStruct, "Module2"); // Should skip // Should only have one instance try std.testing.expectEqual(@as(usize, 1), registry.argumentCount()); } test "ArgumentRegistry: registerMetadata compatible collision" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const Module1 = struct { verbose: bool, }; const Module2 = struct { verbose: bool, }; try registry.registerMetadata(Module1, "Module1"); try registry.registerMetadata(Module2, "Module2"); // Both should register successfully (compatible types) const arg = registry.getArgument("verbose"); try std.testing.expect(arg != null); try std.testing.expectEqual(ArgumentType.bool, arg.?.arg_type); // Both modules should be listed const modules = registry.getModulesForArg("verbose"); try std.testing.expect(modules != null); try std.testing.expectEqual(@as(usize, 2), modules.?.items.len); } test "ArgumentRegistry: registerMetadata incompatible collision" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const Module1 = struct { verbose: bool, }; const Module2 = struct { verbose: u32, // Different type! }; try registry.registerMetadata(Module1, "Module1"); // Should fail with incompatible type error try std.testing.expectError( error.IncompatibleArgumentType, registry.registerMetadata(Module2, "Module2") ); } test "ArgumentRegistry: registerMetadata short flag collision compatible" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const Module1 = struct { verbose: bool, pub const meta = .{ .verbose = .{ .short = 'v' }, }; }; const Module2 = struct { validate: bool, pub const meta = .{ .validate = .{ .short = 'v' }, }; }; try registry.registerMetadata(Module1, "Module1"); try registry.registerMetadata(Module2, "Module2"); // Both should work (same type) try std.testing.expect(registry.hasArgument("verbose")); try std.testing.expect(registry.hasArgument("validate")); try std.testing.expect(registry.hasArgument("v")); } test "ArgumentRegistry: registerMetadata short flag collision incompatible" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const Module1 = struct { verbose: bool, pub const meta = .{ .verbose = .{ .short = 'v' }, }; }; const Module2 = struct { value: u32, pub const meta = .{ .value = .{ .short = 'v' }, }; }; try registry.registerMetadata(Module1, "Module1"); // Should fail due to incompatible short flag try std.testing.expectError( error.IncompatibleArgumentType, registry.registerMetadata(Module2, "Module2") ); } test "ArgumentRegistry: registerMetadata with optional fields" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const TestStruct = struct { verbose: bool, count: ?u32, }; try registry.registerMetadata(TestStruct, "TestModule"); // Both should be registered const verbose_arg = registry.getArgument("verbose"); const count_arg = registry.getArgument("count"); try std.testing.expect(verbose_arg != null); try std.testing.expect(count_arg != null); // verbose is required (non-optional) try std.testing.expectEqual(true, verbose_arg.?.required); // count is not required (optional) try std.testing.expectEqual(false, count_arg.?.required); } test "ArgumentRegistry: registerMetadata with enum" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); const LogLevel = enum { debug, info, warn, @"error" }; const TestStruct = struct { logLevel: LogLevel, }; try registry.registerMetadata(TestStruct, "TestModule"); // TODO: Field names aren't converted to kebab-case yet, using direct name const arg = registry.getArgument("logLevel"); try std.testing.expect(arg != null); try std.testing.expectEqual(ArgumentType.enum_type, arg.?.arg_type); // TODO: Re-enable when enum value extraction is fixed // try std.testing.expectEqual(@as(usize, 4), arg.?.enum_values.len); } test "ArgumentRegistry: hasArgument" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); try std.testing.expect(!registry.hasArgument("verbose")); const TestStruct = struct { verbose: bool }; try registry.registerMetadata(TestStruct, "Module"); try std.testing.expect(registry.hasArgument("verbose")); } test "ArgumentRegistry: argumentCount" { var registry = ArgumentRegistry.init(std.testing.allocator); defer registry.deinit(); try std.testing.expectEqual(@as(usize, 0), registry.argumentCount()); const TestStruct = struct { verbose: bool, count: u32, output: []const u8, }; try registry.registerMetadata(TestStruct, "Module"); try std.testing.expectEqual(@as(usize, 3), registry.argumentCount()); }