diff --git a/lib/hero/heromodels/calendar_event_test.v b/lib/hero/heromodels/calendar_event_test.v new file mode 100644 index 00000000..17db264e --- /dev/null +++ b/lib/hero/heromodels/calendar_event_test.v @@ -0,0 +1,537 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused test + +module heromodels + +import freeflowuniverse.herolib.hero.heromodels + +// Test CalendarEvent model CRUD operations +fn test_calendar_event_new() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test creating a new calendar event with all fields + mut event := mydb.calendar_event.new( + name: 'Test Event' + description: 'A test calendar event for unit testing' + title: 'Team Meeting' + start_time: '2024-01-15 10:00:00' + end_time: '2024-01-15 11:00:00' + location: 'Conference Room A' + attendees: [u32(1), 2, 3] + fs_items: [u32(10), 20] + calendar_id: 1 + status: .published + is_all_day: false + is_recurring: false + recurrence: [] + reminder_mins: [15, 30] + color: '#FF0000' + timezone: 'UTC' + securitypolicy: 1 + tags: ['meeting', 'team'] + comments: [] + ) or { panic('Failed to create calendar event: ${err}') } + + // Verify the event was created with correct values + assert event.name == 'Test Event' + assert event.description == 'A test calendar event for unit testing' + assert event.title == 'Team Meeting' + assert event.location == 'Conference Room A' + assert event.attendees.len == 3 + assert event.attendees[0] == 1 + assert event.fs_items.len == 2 + assert event.fs_items[0] == 10 + assert event.calendar_id == 1 + assert event.status == .published + assert event.is_all_day == false + assert event.is_recurring == false + assert event.reminder_mins.len == 2 + assert event.reminder_mins[0] == 15 + assert event.color == '#FF0000' + assert event.timezone == 'UTC' + assert event.id == 0 // Should be 0 before saving + assert event.start_time > 0 // Should have Unix timestamp + assert event.end_time > event.start_time + assert event.updated_at > 0 +} + +fn test_calendar_event_set_and_get() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a calendar event + mut event := mydb.calendar_event.new( + name: 'Work Event' + description: 'Important work meeting' + title: 'Project Review' + start_time: '2024-02-20 14:00:00' + end_time: '2024-02-20 16:00:00' + location: 'Office Building' + attendees: [u32(5), 6, 7, 8] + fs_items: [u32(100)] + calendar_id: 2 + status: .draft + is_all_day: false + is_recurring: true + recurrence: [ + RecurrenceRule{ + frequency: .weekly + interval: 1 + until: 0 + count: 10 + by_weekday: [1, 3, 5] // Monday, Wednesday, Friday + by_monthday: [] + }, + ] + reminder_mins: [5, 15, 60] + color: '#00FF00' + timezone: 'America/New_York' + securitypolicy: 2 + tags: ['work', 'project', 'review'] + comments: [] + ) or { panic('Failed to create calendar event: ${err}') } + + // Save the event + mydb.calendar_event.set(mut event) or { panic('Failed to save calendar event: ${err}') } + + // Verify ID was assigned + assert event.id > 0 + original_id := event.id + + // Retrieve the event + retrieved_event := mydb.calendar_event.get(event.id) or { + panic('Failed to get calendar event: ${err}') + } + + // Verify all fields match + assert retrieved_event.id == original_id + assert retrieved_event.name == 'Work Event' + assert retrieved_event.description == 'Important work meeting' + assert retrieved_event.title == 'Project Review' + assert retrieved_event.location == 'Office Building' + assert retrieved_event.attendees.len == 4 + assert retrieved_event.attendees[0] == 5 + assert retrieved_event.fs_items.len == 1 + assert retrieved_event.fs_items[0] == 100 + assert retrieved_event.calendar_id == 2 + assert retrieved_event.status == .draft + assert retrieved_event.is_all_day == false + assert retrieved_event.is_recurring == true + assert retrieved_event.recurrence.len == 1 + assert retrieved_event.recurrence[0].frequency == .weekly + assert retrieved_event.recurrence[0].interval == 1 + assert retrieved_event.recurrence[0].count == 10 + assert retrieved_event.recurrence[0].by_weekday.len == 3 + assert retrieved_event.reminder_mins.len == 3 + assert retrieved_event.reminder_mins[0] == 5 + assert retrieved_event.color == '#00FF00' + assert retrieved_event.timezone == 'America/New_York' + assert retrieved_event.created_at > 0 + assert retrieved_event.updated_at > 0 +} + +fn test_calendar_event_update() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save an event + mut event := mydb.calendar_event.new( + name: 'Original Event' + description: 'Original description' + title: 'Original Title' + start_time: '2024-03-10 09:00:00' + end_time: '2024-03-10 10:00:00' + location: 'Room 1' + attendees: [u32(1)] + fs_items: []u32{} + calendar_id: 1 + status: .draft + is_all_day: false + is_recurring: false + recurrence: [] + reminder_mins: [10] + color: '#0000FF' + timezone: 'UTC' + securitypolicy: 1 + tags: ['original'] + comments: [] + ) or { panic('Failed to create calendar event: ${err}') } + + mydb.calendar_event.set(mut event) or { panic('Failed to save calendar event: ${err}') } + original_id := event.id + original_created_at := event.created_at + original_updated_at := event.updated_at + + // Update the event + event.name = 'Updated Event' + event.description = 'Updated description' + event.title = 'Updated Title' + event.location = 'Room 2' + event.attendees = [u32(1), 2, 3] + event.fs_items = [u32(50), 60] + event.calendar_id = 2 + event.status = .published + event.is_all_day = true + event.is_recurring = true + event.recurrence = [ + RecurrenceRule{ + frequency: .daily + interval: 2 + until: 0 + count: 5 + by_weekday: [] + by_monthday: [] + }, + ] + event.reminder_mins = [5, 30, 120] + event.color = '#FFFF00' + event.timezone = 'Europe/London' + + mydb.calendar_event.set(mut event) or { panic('Failed to update calendar event: ${err}') } + + // Verify ID remains the same and updated_at is set + assert event.id == original_id + assert event.created_at == original_created_at + assert event.updated_at >= original_updated_at + + // Retrieve and verify updates + updated_event := mydb.calendar_event.get(event.id) or { + panic('Failed to get updated calendar event: ${err}') + } + assert updated_event.name == 'Updated Event' + assert updated_event.description == 'Updated description' + assert updated_event.title == 'Updated Title' + assert updated_event.location == 'Room 2' + assert updated_event.attendees.len == 3 + assert updated_event.fs_items.len == 2 + assert updated_event.calendar_id == 2 + assert updated_event.status == .published + assert updated_event.is_all_day == true + assert updated_event.is_recurring == true + assert updated_event.recurrence.len == 1 + assert updated_event.recurrence[0].frequency == .daily + assert updated_event.recurrence[0].interval == 2 + assert updated_event.reminder_mins.len == 3 + assert updated_event.color == '#FFFF00' + assert updated_event.timezone == 'Europe/London' +} + +fn test_calendar_event_exist() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test non-existent event + exists := mydb.calendar_event.exist(999) or { panic('Failed to check existence: ${err}') } + assert exists == false + + // Create and save an event + mut event := mydb.calendar_event.new( + name: 'Existence Test' + description: 'Testing existence' + title: 'Test Event' + start_time: '2024-04-01 12:00:00' + end_time: '2024-04-01 13:00:00' + location: 'Test Location' + attendees: []u32{} + fs_items: []u32{} + calendar_id: 1 + status: .published + is_all_day: false + is_recurring: false + recurrence: [] + reminder_mins: [] + color: '#FF00FF' + timezone: 'UTC' + securitypolicy: 1 + tags: ['test'] + comments: [] + ) or { panic('Failed to create calendar event: ${err}') } + + mydb.calendar_event.set(mut event) or { panic('Failed to save calendar event: ${err}') } + + // Test existing event + exists_after_save := mydb.calendar_event.exist(event.id) or { + panic('Failed to check existence: ${err}') + } + assert exists_after_save == true +} + +fn test_calendar_event_delete() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save an event + mut event := mydb.calendar_event.new( + name: 'To Be Deleted' + description: 'This event will be deleted' + title: 'Delete Me' + start_time: '2024-05-01 08:00:00' + end_time: '2024-05-01 09:00:00' + location: 'Nowhere' + attendees: []u32{} + fs_items: []u32{} + calendar_id: 1 + status: .cancelled + is_all_day: false + is_recurring: false + recurrence: [] + reminder_mins: [] + color: '#000000' + timezone: 'UTC' + securitypolicy: 1 + tags: [] + comments: [] + ) or { panic('Failed to create calendar event: ${err}') } + + mydb.calendar_event.set(mut event) or { panic('Failed to save calendar event: ${err}') } + event_id := event.id + + // Verify it exists + exists_before := mydb.calendar_event.exist(event_id) or { + panic('Failed to check existence: ${err}') + } + assert exists_before == true + + // Delete the event + mydb.calendar_event.delete(event_id) or { panic('Failed to delete calendar event: ${err}') } + + // Verify it no longer exists + exists_after := mydb.calendar_event.exist(event_id) or { + panic('Failed to check existence: ${err}') + } + assert exists_after == false + + // Verify get fails + if _ := mydb.calendar_event.get(event_id) { + panic('Should not be able to get deleted calendar event') + } +} + +fn test_calendar_event_list() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Clear any existing events by creating a fresh DB + mydb = heromodels.new() or { panic('Failed to create fresh DB: ${err}') } + + // Initially should be empty + initial_list := mydb.calendar_event.list() or { + panic('Failed to list calendar events: ${err}') + } + initial_count := initial_list.len + + // Create multiple events + mut event1 := mydb.calendar_event.new( + name: 'Event 1' + description: 'First event' + title: 'Morning Meeting' + start_time: '2024-06-01 09:00:00' + end_time: '2024-06-01 10:00:00' + location: 'Room A' + attendees: [u32(1)] + fs_items: []u32{} + calendar_id: 1 + status: .published + is_all_day: false + is_recurring: false + recurrence: [] + reminder_mins: [15] + color: '#FF0000' + timezone: 'UTC' + securitypolicy: 1 + tags: ['morning'] + comments: [] + ) or { panic('Failed to create event1: ${err}') } + + mut event2 := mydb.calendar_event.new( + name: 'Event 2' + description: 'Second event' + title: 'Afternoon Workshop' + start_time: '2024-06-01 14:00:00' + end_time: '2024-06-01 17:00:00' + location: 'Room B' + attendees: [u32(2), 3] + fs_items: [u32(1), 2, 3] + calendar_id: 2 + status: .draft + is_all_day: false + is_recurring: true + recurrence: [ + RecurrenceRule{ + frequency: .monthly + interval: 1 + until: 0 + count: 12 + by_weekday: [] + by_monthday: [1] + }, + ] + reminder_mins: [30, 60] + color: '#00FF00' + timezone: 'America/New_York' + securitypolicy: 2 + tags: ['workshop', 'afternoon'] + comments: [] + ) or { panic('Failed to create event2: ${err}') } + + // Save both events + mydb.calendar_event.set(mut event1) or { panic('Failed to save event1: ${err}') } + mydb.calendar_event.set(mut event2) or { panic('Failed to save event2: ${err}') } + + // List events + event_list := mydb.calendar_event.list() or { panic('Failed to list calendar events: ${err}') } + + // Should have 2 more events than initially + assert event_list.len == initial_count + 2 + + // Find our events in the list + mut found_event1 := false + mut found_event2 := false + + for evt in event_list { + if evt.name == 'Event 1' { + found_event1 = true + assert evt.title == 'Morning Meeting' + assert evt.location == 'Room A' + assert evt.status == .published + assert evt.is_recurring == false + } + if evt.name == 'Event 2' { + found_event2 = true + assert evt.title == 'Afternoon Workshop' + assert evt.location == 'Room B' + assert evt.status == .draft + assert evt.is_recurring == true + assert evt.recurrence.len == 1 + assert evt.recurrence[0].frequency == .monthly + } + } + + assert found_event1 == true + assert found_event2 == true +} + +fn test_calendar_event_edge_cases() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test empty strings and minimal data + mut event := mydb.calendar_event.new( + name: '' + description: '' + title: '' + start_time: '2024-01-01 00:00:00' + end_time: '2024-01-01 00:01:00' + location: '' + attendees: []u32{} + fs_items: []u32{} + calendar_id: 0 + status: .draft + is_all_day: false + is_recurring: false + recurrence: [] + reminder_mins: [] + color: '' + timezone: '' + securitypolicy: 0 + tags: [] + comments: [] + ) or { panic('Failed to create event with empty strings: ${err}') } + + mydb.calendar_event.set(mut event) or { + panic('Failed to save event with empty strings: ${err}') + } + + retrieved := mydb.calendar_event.get(event.id) or { + panic('Failed to get event with empty strings: ${err}') + } + assert retrieved.name == '' + assert retrieved.description == '' + assert retrieved.title == '' + assert retrieved.location == '' + assert retrieved.color == '' + assert retrieved.timezone == '' + assert retrieved.attendees.len == 0 + assert retrieved.fs_items.len == 0 + assert retrieved.reminder_mins.len == 0 + + // Test all-day event + mut all_day_event := mydb.calendar_event.new( + name: 'All Day Event' + description: 'This is an all-day event' + title: 'Holiday' + start_time: '2024-12-25 00:00:00' + end_time: '2024-12-25 23:59:59' + location: 'Everywhere' + attendees: []u32{} + fs_items: []u32{} + calendar_id: 1 + status: .published + is_all_day: true + is_recurring: false + recurrence: [] + reminder_mins: [] + color: '#FF0000' + timezone: 'UTC' + securitypolicy: 1 + tags: ['holiday'] + comments: [] + ) or { panic('Failed to create all-day event: ${err}') } + + mydb.calendar_event.set(mut all_day_event) or { panic('Failed to save all-day event: ${err}') } + + all_day_retrieved := mydb.calendar_event.get(all_day_event.id) or { + panic('Failed to get all-day event: ${err}') + } + assert all_day_retrieved.is_all_day == true + assert all_day_retrieved.title == 'Holiday' + + // Test complex recurring event + mut complex_event := mydb.calendar_event.new( + name: 'Complex Recurring Event' + description: 'Event with complex recurrence rules' + title: 'Weekly Team Standup' + start_time: '2024-01-01 10:00:00' + end_time: '2024-01-01 10:30:00' + location: 'Conference Room' + attendees: []u32{len: 50, init: u32(index + 1)} // 50 attendees + fs_items: []u32{len: 20, init: u32(index + 100)} // 20 files + calendar_id: 1 + status: .published + is_all_day: false + is_recurring: true + recurrence: [ + RecurrenceRule{ + frequency: .weekly + interval: 1 + until: 0 + count: 52 // One year + by_weekday: [1, 2, 3, 4, 5] // Weekdays + by_monthday: [] + }, + RecurrenceRule{ + frequency: .monthly + interval: 1 + until: 0 + count: 12 + by_weekday: [] + by_monthday: [1, 15] // 1st and 15th of month + }, + ] + reminder_mins: [5, 15, 30, 60, 120, 1440] // Multiple reminders + color: '#123456' + timezone: 'America/Los_Angeles' + securitypolicy: 3 + tags: ['standup', 'team', 'recurring', 'important'] + comments: [] + ) or { panic('Failed to create complex event: ${err}') } + + mydb.calendar_event.set(mut complex_event) or { panic('Failed to save complex event: ${err}') } + + complex_retrieved := mydb.calendar_event.get(complex_event.id) or { + panic('Failed to get complex event: ${err}') + } + assert complex_retrieved.attendees.len == 50 + assert complex_retrieved.fs_items.len == 20 + assert complex_retrieved.recurrence.len == 2 + assert complex_retrieved.recurrence[0].frequency == .weekly + assert complex_retrieved.recurrence[1].frequency == .monthly + assert complex_retrieved.recurrence[0].by_weekday.len == 5 + assert complex_retrieved.recurrence[1].by_monthday.len == 2 + assert complex_retrieved.reminder_mins.len == 6 + assert complex_retrieved.reminder_mins[0] == 5 + assert complex_retrieved.reminder_mins[5] == 1440 +} diff --git a/lib/hero/heromodels/calendar_test.v b/lib/hero/heromodels/calendar_test.v new file mode 100644 index 00000000..b8e0a256 --- /dev/null +++ b/lib/hero/heromodels/calendar_test.v @@ -0,0 +1,289 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused test + +module heromodels + +import freeflowuniverse.herolib.hero.heromodels + +// Test Calendar model CRUD operations +fn test_calendar_new() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test creating a new calendar with all fields + mut calendar := mydb.calendar.new( + name: 'Test Calendar' + description: 'A test calendar for unit testing' + color: '#FF0000' + timezone: 'UTC' + is_public: true + events: []u32{} + ) or { panic('Failed to create calendar: ${err}') } + + // Verify the calendar was created with correct values + assert calendar.name == 'Test Calendar' + assert calendar.description == 'A test calendar for unit testing' + assert calendar.color == '#FF0000' + assert calendar.timezone == 'UTC' + assert calendar.is_public == true + assert calendar.events.len == 0 + assert calendar.id == 0 // Should be 0 before saving + assert calendar.updated_at > 0 // Should have timestamp +} + +fn test_calendar_set_and_get() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a calendar + mut calendar := mydb.calendar.new( + name: 'Work Calendar' + description: 'Calendar for work events' + color: '#0000FF' + timezone: 'America/New_York' + is_public: false + events: [u32(1), 2, 3] + ) or { panic('Failed to create calendar: ${err}') } + + // Save the calendar + mydb.calendar.set(mut calendar) or { panic('Failed to save calendar: ${err}') } + + // Verify ID was assigned + assert calendar.id > 0 + original_id := calendar.id + + // Retrieve the calendar + retrieved_calendar := mydb.calendar.get(calendar.id) or { + panic('Failed to get calendar: ${err}') + } + + // Verify all fields match + assert retrieved_calendar.id == original_id + assert retrieved_calendar.name == 'Work Calendar' + assert retrieved_calendar.description == 'Calendar for work events' + assert retrieved_calendar.color == '#0000FF' + assert retrieved_calendar.timezone == 'America/New_York' + assert retrieved_calendar.is_public == false + assert retrieved_calendar.events.len == 3 + assert retrieved_calendar.events[0] == 1 + assert retrieved_calendar.events[1] == 2 + assert retrieved_calendar.events[2] == 3 + assert retrieved_calendar.created_at > 0 + assert retrieved_calendar.updated_at > 0 +} + +fn test_calendar_update() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a calendar + mut calendar := mydb.calendar.new( + name: 'Original Calendar' + description: 'Original description' + color: '#00FF00' + timezone: 'UTC' + is_public: true + events: []u32{} + ) or { panic('Failed to create calendar: ${err}') } + + mydb.calendar.set(mut calendar) or { panic('Failed to save calendar: ${err}') } + original_id := calendar.id + original_created_at := calendar.created_at + original_updated_at := calendar.updated_at + + // Update the calendar + calendar.name = 'Updated Calendar' + calendar.description = 'Updated description' + calendar.color = '#FFFF00' + calendar.timezone = 'Europe/London' + calendar.is_public = false + calendar.events = [u32(10), 20] + + mydb.calendar.set(mut calendar) or { panic('Failed to update calendar: ${err}') } + + // Verify ID remains the same and updated_at is set (may be same if very fast) + assert calendar.id == original_id + assert calendar.created_at == original_created_at + assert calendar.updated_at >= original_updated_at + + // Retrieve and verify updates + updated_calendar := mydb.calendar.get(calendar.id) or { + panic('Failed to get updated calendar: ${err}') + } + assert updated_calendar.name == 'Updated Calendar' + assert updated_calendar.description == 'Updated description' + assert updated_calendar.color == '#FFFF00' + assert updated_calendar.timezone == 'Europe/London' + assert updated_calendar.is_public == false + assert updated_calendar.events.len == 2 + assert updated_calendar.events[0] == 10 + assert updated_calendar.events[1] == 20 +} + +fn test_calendar_exist() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test non-existent calendar + exists := mydb.calendar.exist(999) or { panic('Failed to check existence: ${err}') } + assert exists == false + + // Create and save a calendar + mut calendar := mydb.calendar.new( + name: 'Existence Test' + description: 'Testing existence' + color: '#FF00FF' + timezone: 'UTC' + is_public: true + events: []u32{} + ) or { panic('Failed to create calendar: ${err}') } + + mydb.calendar.set(mut calendar) or { panic('Failed to save calendar: ${err}') } + + // Test existing calendar + exists_after_save := mydb.calendar.exist(calendar.id) or { + panic('Failed to check existence: ${err}') + } + assert exists_after_save == true +} + +fn test_calendar_delete() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a calendar + mut calendar := mydb.calendar.new( + name: 'To Be Deleted' + description: 'This calendar will be deleted' + color: '#000000' + timezone: 'UTC' + is_public: false + events: []u32{} + ) or { panic('Failed to create calendar: ${err}') } + + mydb.calendar.set(mut calendar) or { panic('Failed to save calendar: ${err}') } + calendar_id := calendar.id + + // Verify it exists + exists_before := mydb.calendar.exist(calendar_id) or { + panic('Failed to check existence: ${err}') + } + assert exists_before == true + + // Delete the calendar + mydb.calendar.delete(calendar_id) or { panic('Failed to delete calendar: ${err}') } + + // Verify it no longer exists + exists_after := mydb.calendar.exist(calendar_id) or { + panic('Failed to check existence: ${err}') + } + assert exists_after == false + + // Verify get fails + if _ := mydb.calendar.get(calendar_id) { + panic('Should not be able to get deleted calendar') + } +} + +fn test_calendar_list() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Clear any existing calendars by creating a fresh DB + mydb = heromodels.new() or { panic('Failed to create fresh DB: ${err}') } + + // Initially should be empty + initial_list := mydb.calendar.list() or { panic('Failed to list calendars: ${err}') } + initial_count := initial_list.len + + // Create multiple calendars + mut calendar1 := mydb.calendar.new( + name: 'Calendar 1' + description: 'First calendar' + color: '#FF0000' + timezone: 'UTC' + is_public: true + events: []u32{} + ) or { panic('Failed to create calendar1: ${err}') } + + mut calendar2 := mydb.calendar.new( + name: 'Calendar 2' + description: 'Second calendar' + color: '#00FF00' + timezone: 'America/New_York' + is_public: false + events: [u32(1), 2] + ) or { panic('Failed to create calendar2: ${err}') } + + // Save both calendars + mydb.calendar.set(mut calendar1) or { panic('Failed to save calendar1: ${err}') } + mydb.calendar.set(mut calendar2) or { panic('Failed to save calendar2: ${err}') } + + // List calendars + calendar_list := mydb.calendar.list() or { panic('Failed to list calendars: ${err}') } + + // Should have 2 more calendars than initially + assert calendar_list.len == initial_count + 2 + + // Find our calendars in the list + mut found_calendar1 := false + mut found_calendar2 := false + + for cal in calendar_list { + if cal.name == 'Calendar 1' { + found_calendar1 = true + assert cal.description == 'First calendar' + assert cal.color == '#FF0000' + assert cal.is_public == true + } + if cal.name == 'Calendar 2' { + found_calendar2 = true + assert cal.description == 'Second calendar' + assert cal.color == '#00FF00' + assert cal.is_public == false + assert cal.events.len == 2 + } + } + + assert found_calendar1 == true + assert found_calendar2 == true +} + +fn test_calendar_edge_cases() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test empty strings + mut calendar := mydb.calendar.new( + name: '' + description: '' + color: '' + timezone: '' + is_public: false + events: []u32{} + ) or { panic('Failed to create calendar with empty strings: ${err}') } + + mydb.calendar.set(mut calendar) or { + panic('Failed to save calendar with empty strings: ${err}') + } + + retrieved := mydb.calendar.get(calendar.id) or { + panic('Failed to get calendar with empty strings: ${err}') + } + assert retrieved.name == '' + assert retrieved.description == '' + assert retrieved.color == '' + assert retrieved.timezone == '' + + // Test large events array + large_events := []u32{len: 1000, init: u32(index)} + mut large_calendar := mydb.calendar.new( + name: 'Large Calendar' + description: 'Calendar with many events' + color: '#123456' + timezone: 'UTC' + is_public: true + events: large_events + ) or { panic('Failed to create large calendar: ${err}') } + + mydb.calendar.set(mut large_calendar) or { panic('Failed to save large calendar: ${err}') } + + large_retrieved := mydb.calendar.get(large_calendar.id) or { + panic('Failed to get large calendar: ${err}') + } + assert large_retrieved.events.len == 1000 + assert large_retrieved.events[0] == 0 + assert large_retrieved.events[999] == 999 +} diff --git a/lib/hero/heromodels/chat_group_test.v b/lib/hero/heromodels/chat_group_test.v new file mode 100644 index 00000000..bec0bb5d --- /dev/null +++ b/lib/hero/heromodels/chat_group_test.v @@ -0,0 +1,362 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused test + +module heromodels + +import freeflowuniverse.herolib.hero.heromodels { ChatType } +import freeflowuniverse.herolib.data.ourtime + +// Test ChatGroup model CRUD operations +fn test_chat_group_new() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test creating a new chat group with all fields + now := ourtime.now().unix() + mut chat_group := mydb.chat_group.new( + name: 'General Discussion' + description: 'Main channel for general team discussions' + chat_type: .public_channel + last_activity: now + is_archived: false + securitypolicy: 1 + tags: ['general', 'team', 'discussion'] + comments: [] + ) or { panic('Failed to create chat group: ${err}') } + + // Verify the chat group was created with correct values + assert chat_group.name == 'General Discussion' + assert chat_group.description == 'Main channel for general team discussions' + assert chat_group.chat_type == .public_channel + assert chat_group.last_activity == now + assert chat_group.is_archived == false + assert chat_group.id == 0 // Should be 0 before saving + assert chat_group.updated_at > 0 // Should have timestamp +} + +fn test_chat_group_set_and_get() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a chat group + now := ourtime.now().unix() + mut chat_group := mydb.chat_group.new( + name: 'Development Team' + description: 'Private channel for development team coordination' + chat_type: .private_channel + last_activity: now - 3600 // 1 hour ago + is_archived: false + securitypolicy: 2 + tags: ['development', 'private', 'team'] + comments: [] + ) or { panic('Failed to create chat group: ${err}') } + + // Save the chat group + mydb.chat_group.set(mut chat_group) or { panic('Failed to save chat group: ${err}') } + + // Verify ID was assigned + assert chat_group.id > 0 + original_id := chat_group.id + + // Retrieve the chat group + retrieved_group := mydb.chat_group.get(chat_group.id) or { + panic('Failed to get chat group: ${err}') + } + + // Verify all fields match + assert retrieved_group.id == original_id + assert retrieved_group.name == 'Development Team' + assert retrieved_group.description == 'Private channel for development team coordination' + assert retrieved_group.chat_type == .private_channel + assert retrieved_group.last_activity == now - 3600 + assert retrieved_group.is_archived == false + assert retrieved_group.created_at > 0 + assert retrieved_group.updated_at > 0 +} + +fn test_chat_group_types() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test all chat types + chat_types := [ChatType.public_channel, .private_channel, .direct_message, .group_message] + now := ourtime.now().unix() + + for chat_type in chat_types { + mut chat_group := mydb.chat_group.new( + name: 'Chat ${chat_type}' + description: 'Testing ${chat_type} type' + chat_type: ChatType(chat_type) + last_activity: now + is_archived: false + securitypolicy: 1 + tags: ['test'] + comments: [] + ) or { panic('Failed to create chat group with type ${chat_type}: ${err}') } + + mydb.chat_group.set(mut chat_group) or { + panic('Failed to save chat group with type ${chat_type}: ${err}') + } + + retrieved_group := mydb.chat_group.get(chat_group.id) or { + panic('Failed to get chat group with type ${chat_type}: ${err}') + } + assert retrieved_group.chat_type == ChatType(chat_type) + } +} + +fn test_chat_group_archive() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create an active chat group + now := ourtime.now().unix() + mut chat_group := mydb.chat_group.new( + name: 'Old Project Channel' + description: 'Channel for a completed project' + chat_type: .public_channel + last_activity: now - 86400 * 30 // 30 days ago + is_archived: false + securitypolicy: 1 + tags: ['project', 'completed'] + comments: [] + ) or { panic('Failed to create chat group: ${err}') } + + mydb.chat_group.set(mut chat_group) or { panic('Failed to save chat group: ${err}') } + + // Verify it's not archived initially + retrieved_group := mydb.chat_group.get(chat_group.id) or { + panic('Failed to get chat group: ${err}') + } + assert retrieved_group.is_archived == false + + // Archive the chat group + chat_group.is_archived = true + mydb.chat_group.set(mut chat_group) or { panic('Failed to update chat group: ${err}') } + + // Verify it's now archived + archived_group := mydb.chat_group.get(chat_group.id) or { + panic('Failed to get archived chat group: ${err}') + } + assert archived_group.is_archived == true + assert archived_group.name == 'Old Project Channel' +} + +fn test_chat_group_update() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a chat group + now := ourtime.now().unix() + mut chat_group := mydb.chat_group.new( + name: 'Original Channel' + description: 'Original description' + chat_type: .public_channel + last_activity: now - 86400 + is_archived: false + securitypolicy: 1 + tags: ['original'] + comments: [] + ) or { panic('Failed to create chat group: ${err}') } + + mydb.chat_group.set(mut chat_group) or { panic('Failed to save chat group: ${err}') } + original_id := chat_group.id + original_created_at := chat_group.created_at + original_updated_at := chat_group.updated_at + + // Update the chat group + chat_group.name = 'Updated Channel' + chat_group.description = 'Updated description' + chat_group.chat_type = .private_channel + chat_group.last_activity = now + chat_group.is_archived = true + + mydb.chat_group.set(mut chat_group) or { panic('Failed to update chat group: ${err}') } + + // Verify ID remains the same and updated_at is set + assert chat_group.id == original_id + assert chat_group.created_at == original_created_at + assert chat_group.updated_at >= original_updated_at + + // Retrieve and verify updates + updated_group := mydb.chat_group.get(chat_group.id) or { + panic('Failed to get updated chat group: ${err}') + } + assert updated_group.name == 'Updated Channel' + assert updated_group.description == 'Updated description' + assert updated_group.chat_type == .private_channel + assert updated_group.last_activity == now + assert updated_group.is_archived == true +} + +fn test_chat_group_exist() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test non-existent chat group + exists := mydb.chat_group.exist(999) or { panic('Failed to check existence: ${err}') } + assert exists == false + + // Create and save a chat group + mut chat_group := mydb.chat_group.new( + name: 'Existence Test' + description: 'Testing existence' + chat_type: .direct_message + last_activity: ourtime.now().unix() + is_archived: false + securitypolicy: 1 + tags: ['test'] + comments: [] + ) or { panic('Failed to create chat group: ${err}') } + + mydb.chat_group.set(mut chat_group) or { panic('Failed to save chat group: ${err}') } + + // Test existing chat group + exists_after_save := mydb.chat_group.exist(chat_group.id) or { + panic('Failed to check existence: ${err}') + } + assert exists_after_save == true +} + +fn test_chat_group_delete() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a chat group + mut chat_group := mydb.chat_group.new( + name: 'To Be Deleted' + description: 'This chat group will be deleted' + chat_type: .group_message + last_activity: ourtime.now().unix() + is_archived: true + securitypolicy: 1 + tags: [] + comments: [] + ) or { panic('Failed to create chat group: ${err}') } + + mydb.chat_group.set(mut chat_group) or { panic('Failed to save chat group: ${err}') } + group_id := chat_group.id + + // Verify it exists + exists_before := mydb.chat_group.exist(group_id) or { + panic('Failed to check existence: ${err}') + } + assert exists_before == true + + // Delete the chat group + mydb.chat_group.delete(group_id) or { panic('Failed to delete chat group: ${err}') } + + // Verify it no longer exists + exists_after := mydb.chat_group.exist(group_id) or { + panic('Failed to check existence: ${err}') + } + assert exists_after == false + + // Verify get fails + if _ := mydb.chat_group.get(group_id) { + panic('Should not be able to get deleted chat group') + } +} + +fn test_chat_group_list() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Clear any existing chat groups by creating a fresh DB + mydb = heromodels.new() or { panic('Failed to create fresh DB: ${err}') } + + // Initially should be empty + initial_list := mydb.chat_group.list() or { panic('Failed to list chat groups: ${err}') } + initial_count := initial_list.len + + // Create multiple chat groups + now := ourtime.now().unix() + mut group1 := mydb.chat_group.new( + name: 'Public Channel' + description: 'Public discussion channel' + chat_type: .public_channel + last_activity: now + is_archived: false + securitypolicy: 1 + tags: ['public', 'discussion'] + comments: [] + ) or { panic('Failed to create group1: ${err}') } + + mut group2 := mydb.chat_group.new( + name: 'Private Team Chat' + description: 'Private team coordination' + chat_type: .private_channel + last_activity: now - 3600 + is_archived: false + securitypolicy: 2 + tags: ['private', 'team'] + comments: [] + ) or { panic('Failed to create group2: ${err}') } + + // Save both chat groups + mydb.chat_group.set(mut group1) or { panic('Failed to save group1: ${err}') } + mydb.chat_group.set(mut group2) or { panic('Failed to save group2: ${err}') } + + // List chat groups + group_list := mydb.chat_group.list() or { panic('Failed to list chat groups: ${err}') } + + // Should have 2 more chat groups than initially + assert group_list.len == initial_count + 2 + + // Find our chat groups in the list + mut found_group1 := false + mut found_group2 := false + + for grp in group_list { + if grp.name == 'Public Channel' { + found_group1 = true + assert grp.chat_type == .public_channel + assert grp.is_archived == false + } + if grp.name == 'Private Team Chat' { + found_group2 = true + assert grp.chat_type == .private_channel + assert grp.is_archived == false + } + } + + assert found_group1 == true + assert found_group2 == true +} + +fn test_chat_group_edge_cases() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test chat group with empty fields + mut empty_group := mydb.chat_group.new( + name: '' + description: '' + chat_type: .direct_message + last_activity: 0 + is_archived: false + securitypolicy: 0 + tags: [] + comments: [] + ) or { panic('Failed to create empty chat group: ${err}') } + + mydb.chat_group.set(mut empty_group) or { panic('Failed to save empty chat group: ${err}') } + + retrieved_empty := mydb.chat_group.get(empty_group.id) or { + panic('Failed to get empty chat group: ${err}') + } + assert retrieved_empty.name == '' + assert retrieved_empty.description == '' + assert retrieved_empty.last_activity == 0 + assert retrieved_empty.is_archived == false + + // Test archived direct message + now := ourtime.now().unix() + mut dm_group := mydb.chat_group.new( + name: 'DM: Alice & Bob' + description: 'Direct message between Alice and Bob' + chat_type: .direct_message + last_activity: now - 86400 * 7 // 1 week ago + is_archived: true + securitypolicy: 1 + tags: ['dm', 'archived'] + comments: [] + ) or { panic('Failed to create DM group: ${err}') } + + mydb.chat_group.set(mut dm_group) or { panic('Failed to save DM group: ${err}') } + + retrieved_dm := mydb.chat_group.get(dm_group.id) or { panic('Failed to get DM group: ${err}') } + assert retrieved_dm.chat_type == .direct_message + assert retrieved_dm.is_archived == true + assert retrieved_dm.last_activity == now - 86400 * 7 +} diff --git a/lib/hero/heromodels/chat_message_test.v b/lib/hero/heromodels/chat_message_test.v new file mode 100644 index 00000000..46ebab50 --- /dev/null +++ b/lib/hero/heromodels/chat_message_test.v @@ -0,0 +1,396 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused test + +module heromodels + +import freeflowuniverse.herolib.hero.heromodels +import freeflowuniverse.herolib.data.ourtime + +// Test ChatMessage model CRUD operations +fn test_chat_message_new() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + now := ourtime.now().unix() + + // Test creating a new chat message with all fields + mut message := mydb.chat_message.new( + name: 'MSG-001' + description: 'Important team announcement' + content: 'Hello team! We have an important announcement about the upcoming release.' + chat_group_id: 1 + sender_id: 123 + parent_messages: [ + MessageLink{ + message_id: 100 + link_type: .reply + }, + ] + fs_files: [u32(200), 300] + message_type: .text + status: .sent + reactions: [ + MessageReaction{ + user_id: 456 + emoji: '👍' + timestamp: now + }, + MessageReaction{ + user_id: 789 + emoji: '❤️' + timestamp: now + }, + ] + mentions: [u32(456), 789, 101] + securitypolicy: 1 + tags: ['announcement', 'important'] + comments: [] + ) or { panic('Failed to create chat message: ${err}') } + + // Verify the message was created with correct values + assert message.name == 'MSG-001' + assert message.description == 'Important team announcement' + assert message.content == 'Hello team! We have an important announcement about the upcoming release.' + assert message.chat_group_id == 1 + assert message.sender_id == 123 + assert message.parent_messages.len == 1 + assert message.parent_messages[0].message_id == 100 + assert message.parent_messages[0].link_type == .reply + assert message.fs_files.len == 2 + assert message.fs_files[0] == 200 + assert message.message_type == .text + assert message.status == .sent + assert message.reactions.len == 2 + assert message.reactions[0].user_id == 456 + assert message.reactions[0].emoji == '👍' + assert message.mentions.len == 3 + assert message.mentions[0] == 456 + assert message.id == 0 // Should be 0 before saving + assert message.updated_at > 0 // Should have timestamp +} + +fn test_chat_message_set_and_get() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a chat message + mut message := mydb.chat_message.new( + name: 'MSG-002' + description: 'Simple text message' + content: 'Hey everyone, how is the project going?' + chat_group_id: 2 + sender_id: 456 + parent_messages: []MessageLink{} + fs_files: []u32{} + message_type: .text + status: .delivered + reactions: []MessageReaction{} + mentions: []u32{} + securitypolicy: 1 + tags: ['casual', 'question'] + comments: [] + ) or { panic('Failed to create chat message: ${err}') } + + // Save the message + mydb.chat_message.set(mut message) or { panic('Failed to save chat message: ${err}') } + + // Verify ID was assigned + assert message.id > 0 + original_id := message.id + + // Retrieve the message + retrieved_message := mydb.chat_message.get(message.id) or { + panic('Failed to get chat message: ${err}') + } + + // Verify all fields match + assert retrieved_message.id == original_id + assert retrieved_message.name == 'MSG-002' + assert retrieved_message.description == 'Simple text message' + assert retrieved_message.content == 'Hey everyone, how is the project going?' + assert retrieved_message.chat_group_id == 2 + assert retrieved_message.sender_id == 456 + assert retrieved_message.parent_messages.len == 0 + assert retrieved_message.fs_files.len == 0 + assert retrieved_message.message_type == .text + assert retrieved_message.status == .delivered + assert retrieved_message.reactions.len == 0 + assert retrieved_message.mentions.len == 0 + assert retrieved_message.created_at > 0 + assert retrieved_message.updated_at > 0 +} + +fn test_chat_message_types_and_status() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test different message types + message_types := [heromodels.MessageType.text, .image, .file, .system] + statuses := [heromodels.MessageStatus.sent, .delivered, .read, .failed, .deleted] + + for i, msg_type in message_types { + status := statuses[i % statuses.len] + + mut message := mydb.chat_message.new( + name: 'TEST-${i}' + description: 'Testing ${msg_type} with ${status} status' + content: 'Test message content for ${msg_type}' + chat_group_id: 1 + sender_id: u32(i + 1) + parent_messages: []MessageLink{} + fs_files: []u32{} + message_type: heromodels.MessageType(msg_type) + status: heromodels.MessageStatus(status) + reactions: []MessageReaction{} + mentions: []u32{} + securitypolicy: 1 + tags: ['test'] + comments: [] + ) or { panic('Failed to create message with type ${msg_type}: ${err}') } + + mydb.chat_message.set(mut message) or { + panic('Failed to save message with type ${msg_type}: ${err}') + } + + retrieved_message := mydb.chat_message.get(message.id) or { + panic('Failed to get message with type ${msg_type}: ${err}') + } + assert retrieved_message.message_type == heromodels.MessageType(msg_type) + assert retrieved_message.status == heromodels.MessageStatus(status) + } +} + +fn test_chat_message_reactions() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + now := ourtime.now().unix() + + // Create message with multiple reactions + mut message := mydb.chat_message.new( + name: 'REACT-MSG' + description: 'Message with reactions' + content: 'This is a great idea! 🎉' + chat_group_id: 1 + sender_id: 100 + parent_messages: []MessageLink{} + fs_files: []u32{} + message_type: .text + status: .read + reactions: [ + MessageReaction{ + user_id: 101 + emoji: '👍' + timestamp: now + }, + MessageReaction{ + user_id: 102 + emoji: '❤️' + timestamp: now + }, + MessageReaction{ + user_id: 103 + emoji: '😂' + timestamp: now + }, + MessageReaction{ + user_id: 104 + emoji: '👍' + timestamp: now + }, + ] + mentions: []u32{} + securitypolicy: 1 + tags: ['positive', 'reactions'] + comments: [] + ) or { panic('Failed to create message with reactions: ${err}') } + + mydb.chat_message.set(mut message) or { panic('Failed to save message with reactions: ${err}') } + + retrieved_message := mydb.chat_message.get(message.id) or { + panic('Failed to get message with reactions: ${err}') + } + + // Verify all reactions are preserved + assert retrieved_message.reactions.len == 4 + + // Count reaction types + mut thumbs_up_count := 0 + mut heart_count := 0 + mut laugh_count := 0 + + for reaction in retrieved_message.reactions { + match reaction.emoji { + '👍' { thumbs_up_count++ } + '❤️' { heart_count++ } + '😂' { laugh_count++ } + else {} + } + } + + assert thumbs_up_count == 2 + assert heart_count == 1 + assert laugh_count == 1 +} + +fn test_chat_message_thread() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create original message + mut original_message := mydb.chat_message.new( + name: 'THREAD-ORIGINAL' + description: 'Original message in thread' + content: 'What do you think about the new feature proposal?' + chat_group_id: 1 + sender_id: 200 + parent_messages: []MessageLink{} + fs_files: []u32{} + message_type: .text + status: .read + reactions: []MessageReaction{} + mentions: []u32{} + securitypolicy: 1 + tags: ['discussion', 'feature'] + comments: [] + ) or { panic('Failed to create original message: ${err}') } + + mydb.chat_message.set(mut original_message) or { + panic('Failed to save original message: ${err}') + } + original_id := original_message.id + + // Create reply message + mut reply_message := mydb.chat_message.new( + name: 'THREAD-REPLY-1' + description: 'Reply to original message' + content: "I think it's a great idea! We should implement it." + chat_group_id: 1 + sender_id: 201 + parent_messages: [ + MessageLink{ + message_id: original_id + link_type: .reply + }, + ] + fs_files: []u32{} + message_type: .text + status: .sent + reactions: []MessageReaction{} + mentions: [u32(200)] // Mention original sender + securitypolicy: 1 + tags: ['reply', 'positive'] + comments: [] + ) or { panic('Failed to create reply message: ${err}') } + + mydb.chat_message.set(mut reply_message) or { panic('Failed to save reply message: ${err}') } + + // Create another reply + mut reply2_message := mydb.chat_message.new( + name: 'THREAD-REPLY-2' + description: 'Second reply to original message' + content: "I agree with @user201. Let's schedule a meeting to discuss details." + chat_group_id: 1 + sender_id: 202 + parent_messages: [ + MessageLink{ + message_id: original_id + link_type: .reply + }, + ] + fs_files: []u32{} + message_type: .text + status: .delivered + reactions: []MessageReaction{} + mentions: [u32(200), 201] // Mention both previous users + securitypolicy: 1 + tags: ['reply', 'meeting'] + comments: [] + ) or { panic('Failed to create second reply message: ${err}') } + + mydb.chat_message.set(mut reply2_message) or { + panic('Failed to save second reply message: ${err}') + } + + // Verify thread structure + retrieved_original := mydb.chat_message.get(original_id) or { + panic('Failed to get original message: ${err}') + } + retrieved_reply1 := mydb.chat_message.get(reply_message.id) or { + panic('Failed to get first reply: ${err}') + } + retrieved_reply2 := mydb.chat_message.get(reply2_message.id) or { + panic('Failed to get second reply: ${err}') + } + + // Original message should have no parent + assert retrieved_original.parent_messages.len == 0 + + // Both replies should reference the original message + assert retrieved_reply1.parent_messages.len == 1 + assert retrieved_reply1.parent_messages[0].message_id == original_id + assert retrieved_reply1.parent_messages[0].link_type == .reply + assert retrieved_reply1.mentions.len == 1 + assert retrieved_reply1.mentions[0] == 200 + + assert retrieved_reply2.parent_messages.len == 1 + assert retrieved_reply2.parent_messages[0].message_id == original_id + assert retrieved_reply2.parent_messages[0].link_type == .reply + assert retrieved_reply2.mentions.len == 2 + assert retrieved_reply2.mentions.contains(200) + assert retrieved_reply2.mentions.contains(201) +} + +fn test_chat_message_update() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + now := ourtime.now().unix() + + // Create and save a message + mut message := mydb.chat_message.new( + name: 'EDIT-MSG' + description: 'Original message' + content: 'Original content' + chat_group_id: 1 + sender_id: 300 + parent_messages: []MessageLink{} + fs_files: []u32{} + message_type: .text + status: .sent + reactions: []MessageReaction{} + mentions: []u32{} + securitypolicy: 1 + tags: ['original'] + comments: [] + ) or { panic('Failed to create chat message: ${err}') } + + mydb.chat_message.set(mut message) or { panic('Failed to save chat message: ${err}') } + original_id := message.id + original_created_at := message.created_at + original_updated_at := message.updated_at + + // Update the message + message.name = 'EDIT-MSG-UPDATED' + message.description = 'Updated message' + message.content = 'Updated content - this message has been edited' + message.status = .read + message.reactions = [ + MessageReaction{ + user_id: 301 + emoji: '👍' + timestamp: now + }, + ] + message.mentions = [u32(302)] + + mydb.chat_message.set(mut message) or { panic('Failed to update chat message: ${err}') } + + // Verify ID remains the same and updated_at is set + assert message.id == original_id + assert message.created_at == original_created_at + assert message.updated_at >= original_updated_at + + // Retrieve and verify updates + updated_message := mydb.chat_message.get(message.id) or { + panic('Failed to get updated chat message: ${err}') + } + assert updated_message.name == 'EDIT-MSG-UPDATED' + assert updated_message.description == 'Updated message' + assert updated_message.content == 'Updated content - this message has been edited' + assert updated_message.status == .read + assert updated_message.reactions.len == 1 + assert updated_message.reactions[0].user_id == 301 + assert updated_message.mentions.len == 1 + assert updated_message.mentions[0] == 302 +} diff --git a/lib/hero/heromodels/comment_test.v b/lib/hero/heromodels/comment_test.v new file mode 100644 index 00000000..f01c955e --- /dev/null +++ b/lib/hero/heromodels/comment_test.v @@ -0,0 +1,276 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused test + +module heromodels + +import freeflowuniverse.herolib.hero.heromodels + +// Test Comment model CRUD operations +fn test_comment_new() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test creating a new comment with all fields + mut comment := mydb.comments.new( + comment: 'This is a test comment' + parent: 0 + author: 123 + ) or { panic('Failed to create comment: ${err}') } + + // Verify the comment was created with correct values + assert comment.comment == 'This is a test comment' + assert comment.parent == 0 + assert comment.author == 123 + assert comment.id == 0 // Should be 0 before saving + assert comment.updated_at > 0 // Should have timestamp +} + +fn test_comment_set_and_get() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a comment + mut comment := mydb.comments.new( + comment: 'Hello, this is my first comment!' + parent: 0 + author: 456 + ) or { panic('Failed to create comment: ${err}') } + + // Save the comment + mydb.comments.set(mut comment) or { panic('Failed to save comment: ${err}') } + + // Verify ID was assigned + assert comment.id > 0 + original_id := comment.id + + // Retrieve the comment + retrieved_comment := mydb.comments.get(comment.id) or { panic('Failed to get comment: ${err}') } + + // Verify all fields match + assert retrieved_comment.id == original_id + assert retrieved_comment.comment == 'Hello, this is my first comment!' + assert retrieved_comment.parent == 0 + assert retrieved_comment.author == 456 + assert retrieved_comment.created_at > 0 + assert retrieved_comment.updated_at > 0 +} + +fn test_comment_reply() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a parent comment + mut parent_comment := mydb.comments.new( + comment: 'This is the parent comment' + parent: 0 + author: 100 + ) or { panic('Failed to create parent comment: ${err}') } + + mydb.comments.set(mut parent_comment) or { panic('Failed to save parent comment: ${err}') } + parent_id := parent_comment.id + + // Create a reply comment + mut reply_comment := mydb.comments.new( + comment: 'This is a reply to the parent comment' + parent: parent_id + author: 200 + ) or { panic('Failed to create reply comment: ${err}') } + + mydb.comments.set(mut reply_comment) or { panic('Failed to save reply comment: ${err}') } + + // Retrieve both comments + retrieved_parent := mydb.comments.get(parent_id) or { panic('Failed to get parent comment: ${err}') } + retrieved_reply := mydb.comments.get(reply_comment.id) or { panic('Failed to get reply comment: ${err}') } + + // Verify parent comment + assert retrieved_parent.comment == 'This is the parent comment' + assert retrieved_parent.parent == 0 + assert retrieved_parent.author == 100 + + // Verify reply comment + assert retrieved_reply.comment == 'This is a reply to the parent comment' + assert retrieved_reply.parent == parent_id + assert retrieved_reply.author == 200 +} + +fn test_comment_update() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a comment + mut comment := mydb.comments.new( + comment: 'Original comment text' + parent: 0 + author: 300 + ) or { panic('Failed to create comment: ${err}') } + + mydb.comments.set(mut comment) or { panic('Failed to save comment: ${err}') } + original_id := comment.id + original_created_at := comment.created_at + original_updated_at := comment.updated_at + + // Update the comment + comment.comment = 'Updated comment text' + comment.parent = 999 + comment.author = 400 + + mydb.comments.set(mut comment) or { panic('Failed to update comment: ${err}') } + + // Verify ID remains the same and updated_at is set + assert comment.id == original_id + assert comment.created_at == original_created_at + assert comment.updated_at >= original_updated_at + + // Retrieve and verify updates + updated_comment := mydb.comments.get(comment.id) or { panic('Failed to get updated comment: ${err}') } + assert updated_comment.comment == 'Updated comment text' + assert updated_comment.parent == 999 + assert updated_comment.author == 400 +} + +fn test_comment_exist() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test non-existent comment + exists := mydb.comments.exist(999) or { panic('Failed to check existence: ${err}') } + assert exists == false + + // Create and save a comment + mut comment := mydb.comments.new( + comment: 'Existence test comment' + parent: 0 + author: 500 + ) or { panic('Failed to create comment: ${err}') } + + mydb.comments.set(mut comment) or { panic('Failed to save comment: ${err}') } + + // Test existing comment + exists_after_save := mydb.comments.exist(comment.id) or { panic('Failed to check existence: ${err}') } + assert exists_after_save == true +} + +fn test_comment_delete() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a comment + mut comment := mydb.comments.new( + comment: 'This comment will be deleted' + parent: 0 + author: 600 + ) or { panic('Failed to create comment: ${err}') } + + mydb.comments.set(mut comment) or { panic('Failed to save comment: ${err}') } + comment_id := comment.id + + // Verify it exists + exists_before := mydb.comments.exist(comment_id) or { panic('Failed to check existence: ${err}') } + assert exists_before == true + + // Delete the comment + mydb.comments.delete(comment_id) or { panic('Failed to delete comment: ${err}') } + + // Verify it no longer exists + exists_after := mydb.comments.exist(comment_id) or { panic('Failed to check existence: ${err}') } + assert exists_after == false + + // Verify get fails + if _ := mydb.comments.get(comment_id) { + panic('Should not be able to get deleted comment') + } +} + +fn test_comment_list() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Clear any existing comments by creating a fresh DB + mydb = heromodels.new() or { panic('Failed to create fresh DB: ${err}') } + + // Initially should be empty + initial_list := mydb.comments.list() or { panic('Failed to list comments: ${err}') } + initial_count := initial_list.len + + // Create multiple comments + mut comment1 := mydb.comments.new( + comment: 'First comment' + parent: 0 + author: 700 + ) or { panic('Failed to create comment1: ${err}') } + + mut comment2 := mydb.comments.new( + comment: 'Second comment' + parent: 0 + author: 800 + ) or { panic('Failed to create comment2: ${err}') } + + // Save both comments + mydb.comments.set(mut comment1) or { panic('Failed to save comment1: ${err}') } + mydb.comments.set(mut comment2) or { panic('Failed to save comment2: ${err}') } + + // List comments + comment_list := mydb.comments.list() or { panic('Failed to list comments: ${err}') } + + // Should have 2 more comments than initially + assert comment_list.len == initial_count + 2 + + // Find our comments in the list + mut found_comment1 := false + mut found_comment2 := false + + for cmt in comment_list { + if cmt.comment == 'First comment' { + found_comment1 = true + assert cmt.author == 700 + assert cmt.parent == 0 + } + if cmt.comment == 'Second comment' { + found_comment2 = true + assert cmt.author == 800 + assert cmt.parent == 0 + } + } + + assert found_comment1 == true + assert found_comment2 == true +} + +fn test_comment_edge_cases() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test empty comment + mut empty_comment := mydb.comments.new( + comment: '' + parent: 0 + author: 0 + ) or { panic('Failed to create empty comment: ${err}') } + + mydb.comments.set(mut empty_comment) or { panic('Failed to save empty comment: ${err}') } + + retrieved_empty := mydb.comments.get(empty_comment.id) or { panic('Failed to get empty comment: ${err}') } + assert retrieved_empty.comment == '' + assert retrieved_empty.parent == 0 + assert retrieved_empty.author == 0 + + // Test very long comment + long_text := 'This is a very long comment. '.repeat(100) + mut long_comment := mydb.comments.new( + comment: long_text + parent: 12345 + author: 99999 + ) or { panic('Failed to create long comment: ${err}') } + + mydb.comments.set(mut long_comment) or { panic('Failed to save long comment: ${err}') } + + retrieved_long := mydb.comments.get(long_comment.id) or { panic('Failed to get long comment: ${err}') } + assert retrieved_long.comment == long_text + assert retrieved_long.parent == 12345 + assert retrieved_long.author == 99999 + + // Test comment with special characters + special_text := 'Comment with special chars: !@#$%^&*()_+-=[]{}|;:,.<>?/~`' + mut special_comment := mydb.comments.new( + comment: special_text + parent: 0 + author: 1000 + ) or { panic('Failed to create special comment: ${err}') } + + mydb.comments.set(mut special_comment) or { panic('Failed to save special comment: ${err}') } + + retrieved_special := mydb.comments.get(special_comment.id) or { panic('Failed to get special comment: ${err}') } + assert retrieved_special.comment == special_text + assert retrieved_special.author == 1000 +} diff --git a/lib/hero/heromodels/group_test.v b/lib/hero/heromodels/group_test.v new file mode 100644 index 00000000..3ae26422 --- /dev/null +++ b/lib/hero/heromodels/group_test.v @@ -0,0 +1,517 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused test + +module heromodels + +import freeflowuniverse.herolib.hero.heromodels { GroupMember, GroupRole } +import freeflowuniverse.herolib.data.ourtime + +// Test Group model CRUD operations +fn test_group_new() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test creating a new group with all fields + now := ourtime.now().unix() + mut group := mydb.group.new( + name: 'Development Team' + description: 'Software development team for the main project' + members: [ + GroupMember{ + user_id: 1 + role: .owner + joined_at: now + }, + GroupMember{ + user_id: 2 + role: .admin + joined_at: now + }, + GroupMember{ + user_id: 3 + role: .writer + joined_at: now + }, + ] + subgroups: [u32(10), 20, 30] + parent_group: 0 + is_public: false + ) or { panic('Failed to create group: ${err}') } + + // Verify the group was created with correct values + assert group.name == 'Development Team' + assert group.description == 'Software development team for the main project' + assert group.members.len == 3 + assert group.members[0].user_id == 1 + assert group.members[0].role == .owner + assert group.members[1].user_id == 2 + assert group.members[1].role == .admin + assert group.members[2].user_id == 3 + assert group.members[2].role == .writer + assert group.subgroups.len == 3 + assert group.subgroups[0] == 10 + assert group.parent_group == 0 + assert group.is_public == false + assert group.id == 0 // Should be 0 before saving + assert group.updated_at > 0 // Should have timestamp +} + +fn test_group_set_and_get() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a group + now := ourtime.now().unix() + mut group := mydb.group.new( + name: 'Marketing Team' + description: 'Marketing and communications team' + members: [ + GroupMember{ + user_id: 100 + role: .owner + joined_at: now - 86400 // 1 day ago + }, + GroupMember{ + user_id: 101 + role: .writer + joined_at: now + }, + ] + subgroups: []u32{} + parent_group: 5 + is_public: true + ) or { panic('Failed to create group: ${err}') } + + // Save the group + mydb.group.set(mut group) or { panic('Failed to save group: ${err}') } + + // Verify ID was assigned + assert group.id > 0 + original_id := group.id + + // Retrieve the group + retrieved_group := mydb.group.get(group.id) or { panic('Failed to get group: ${err}') } + + // Verify all fields match + assert retrieved_group.id == original_id + assert retrieved_group.name == 'Marketing Team' + assert retrieved_group.description == 'Marketing and communications team' + assert retrieved_group.members.len == 2 + assert retrieved_group.members[0].user_id == 100 + assert retrieved_group.members[0].role == .owner + assert retrieved_group.members[1].user_id == 101 + assert retrieved_group.members[1].role == .writer + assert retrieved_group.subgroups.len == 0 + assert retrieved_group.parent_group == 5 + assert retrieved_group.is_public == true + assert retrieved_group.created_at > 0 + assert retrieved_group.updated_at > 0 +} + +fn test_group_roles() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test all group roles + roles := [GroupRole.reader, .writer, .admin, .owner] + now := ourtime.now().unix() + + mut members := []GroupMember{} + for i, role in roles { + members << GroupMember{ + user_id: u32(i + 1) + role: role + joined_at: now + i64(i * 3600) // Different join times + } + } + + mut group := mydb.group.new( + name: 'Role Test Group' + description: 'Testing all group roles' + members: members + subgroups: []u32{} + parent_group: 0 + is_public: false + ) or { panic('Failed to create group: ${err}') } + + mydb.group.set(mut group) or { panic('Failed to save group: ${err}') } + + retrieved_group := mydb.group.get(group.id) or { panic('Failed to get group: ${err}') } + + // Verify all roles are preserved + assert retrieved_group.members.len == 4 + assert retrieved_group.members[0].role == .reader + assert retrieved_group.members[1].role == .writer + assert retrieved_group.members[2].role == .admin + assert retrieved_group.members[3].role == .owner + + // Verify join times are preserved + for i, member in retrieved_group.members { + assert member.joined_at == now + i64(i * 3600) + } +} + +fn test_group_hierarchy() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create parent group + mut parent_group := mydb.group.new( + name: 'Engineering' + description: 'Main engineering group' + members: [ + GroupMember{ + user_id: 1 + role: .owner + joined_at: ourtime.now().unix() + }, + ] + subgroups: []u32{} + parent_group: 0 + is_public: false + ) or { panic('Failed to create parent group: ${err}') } + + mydb.group.set(mut parent_group) or { panic('Failed to save parent group: ${err}') } + parent_id := parent_group.id + + // Create child groups + mut frontend_group := mydb.group.new( + name: 'Frontend Team' + description: 'Frontend development team' + members: [ + GroupMember{ + user_id: 10 + role: .admin + joined_at: ourtime.now().unix() + }, + ] + subgroups: []u32{} + parent_group: parent_id + is_public: false + ) or { panic('Failed to create frontend group: ${err}') } + + mut backend_group := mydb.group.new( + name: 'Backend Team' + description: 'Backend development team' + members: [ + GroupMember{ + user_id: 20 + role: .admin + joined_at: ourtime.now().unix() + }, + ] + subgroups: []u32{} + parent_group: parent_id + is_public: false + ) or { panic('Failed to create backend group: ${err}') } + + mydb.group.set(mut frontend_group) or { panic('Failed to save frontend group: ${err}') } + mydb.group.set(mut backend_group) or { panic('Failed to save backend group: ${err}') } + + // Update parent group with subgroups + parent_group.subgroups = [frontend_group.id, backend_group.id] + mydb.group.set(mut parent_group) or { panic('Failed to update parent group: ${err}') } + + // Verify hierarchy + retrieved_parent := mydb.group.get(parent_id) or { panic('Failed to get parent group: ${err}') } + retrieved_frontend := mydb.group.get(frontend_group.id) or { + panic('Failed to get frontend group: ${err}') + } + retrieved_backend := mydb.group.get(backend_group.id) or { + panic('Failed to get backend group: ${err}') + } + + assert retrieved_parent.subgroups.len == 2 + assert retrieved_parent.subgroups.contains(frontend_group.id) + assert retrieved_parent.subgroups.contains(backend_group.id) + assert retrieved_frontend.parent_group == parent_id + assert retrieved_backend.parent_group == parent_id +} + +fn test_group_update() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a group + now := ourtime.now().unix() + mut group := mydb.group.new( + name: 'Original Group' + description: 'Original description' + members: [ + GroupMember{ + user_id: 1 + role: .reader + joined_at: now + }, + ] + subgroups: []u32{} + parent_group: 0 + is_public: false + ) or { panic('Failed to create group: ${err}') } + + mydb.group.set(mut group) or { panic('Failed to save group: ${err}') } + original_id := group.id + original_created_at := group.created_at + original_updated_at := group.updated_at + + // Update the group + group.name = 'Updated Group' + group.description = 'Updated description' + group.members = [ + GroupMember{ + user_id: 1 + role: .admin + joined_at: now + }, + GroupMember{ + user_id: 2 + role: .writer + joined_at: now + 3600 + }, + ] + group.subgroups = [u32(100), 200] + group.parent_group = 50 + group.is_public = true + + mydb.group.set(mut group) or { panic('Failed to update group: ${err}') } + + // Verify ID remains the same and updated_at is set + assert group.id == original_id + assert group.created_at == original_created_at + assert group.updated_at >= original_updated_at + + // Retrieve and verify updates + updated_group := mydb.group.get(group.id) or { panic('Failed to get updated group: ${err}') } + assert updated_group.name == 'Updated Group' + assert updated_group.description == 'Updated description' + assert updated_group.members.len == 2 + assert updated_group.members[0].role == .admin + assert updated_group.members[1].role == .writer + assert updated_group.subgroups.len == 2 + assert updated_group.subgroups[0] == 100 + assert updated_group.parent_group == 50 + assert updated_group.is_public == true +} + +fn test_group_exist() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test non-existent group with a very high ID that shouldn't exist + exists := mydb.group.exist(999999) or { panic('Failed to check existence: ${err}') } + assert exists == false + + // Create and save a group + mut group := mydb.group.new( + name: 'Existence Test' + description: 'Testing existence' + members: []GroupMember{} + subgroups: []u32{} + parent_group: 0 + is_public: true + ) or { panic('Failed to create group: ${err}') } + + mydb.group.set(mut group) or { panic('Failed to save group: ${err}') } + + // Test existing group + exists_after_save := mydb.group.exist(group.id) or { + panic('Failed to check existence: ${err}') + } + assert exists_after_save == true +} + +fn test_group_delete() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a group + mut group := mydb.group.new( + name: 'To Be Deleted' + description: 'This group will be deleted' + members: []GroupMember{} + subgroups: []u32{} + parent_group: 0 + is_public: false + ) or { panic('Failed to create group: ${err}') } + + mydb.group.set(mut group) or { panic('Failed to save group: ${err}') } + group_id := group.id + + // Verify it exists + exists_before := mydb.group.exist(group_id) or { panic('Failed to check existence: ${err}') } + assert exists_before == true + + // Delete the group + mydb.group.delete(group_id) or { panic('Failed to delete group: ${err}') } + + // Verify it no longer exists + exists_after := mydb.group.exist(group_id) or { panic('Failed to check existence: ${err}') } + assert exists_after == false + + // Verify get fails + if _ := mydb.group.get(group_id) { + panic('Should not be able to get deleted group') + } +} + +fn test_group_list() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Clear any existing groups by creating a fresh DB + mydb = heromodels.new() or { panic('Failed to create fresh DB: ${err}') } + + // Initially should be empty + initial_list := mydb.group.list() or { panic('Failed to list groups: ${err}') } + initial_count := initial_list.len + + // Create multiple groups + now := ourtime.now().unix() + mut group1 := mydb.group.new( + name: 'Group One' + description: 'First test group' + members: [ + GroupMember{ + user_id: 1 + role: .owner + joined_at: now + }, + ] + subgroups: []u32{} + parent_group: 0 + is_public: true + ) or { panic('Failed to create group1: ${err}') } + + mut group2 := mydb.group.new( + name: 'Group Two' + description: 'Second test group' + members: [ + GroupMember{ + user_id: 2 + role: .admin + joined_at: now + }, + GroupMember{ + user_id: 3 + role: .writer + joined_at: now + 3600 + }, + ] + subgroups: [u32(10)] + parent_group: 0 + is_public: false + ) or { panic('Failed to create group2: ${err}') } + + // Save both groups + mydb.group.set(mut group1) or { panic('Failed to save group1: ${err}') } + mydb.group.set(mut group2) or { panic('Failed to save group2: ${err}') } + + // List groups + group_list := mydb.group.list() or { panic('Failed to list groups: ${err}') } + + // Should have 2 more groups than initially + assert group_list.len == initial_count + 2 + + // Find our groups in the list + mut found_group1 := false + mut found_group2 := false + + for grp in group_list { + if grp.name == 'Group One' { + found_group1 = true + assert grp.is_public == true + assert grp.members.len == 1 + assert grp.members[0].role == .owner + } + if grp.name == 'Group Two' { + found_group2 = true + assert grp.is_public == false + assert grp.members.len == 2 + assert grp.subgroups.len == 1 + } + } + + assert found_group1 == true + assert found_group2 == true +} + +fn test_group_edge_cases() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test group with no members + mut empty_group := mydb.group.new( + name: 'Empty Group' + description: 'Group with no members' + members: []GroupMember{} + subgroups: []u32{} + parent_group: 0 + is_public: true + ) or { panic('Failed to create empty group: ${err}') } + + mydb.group.set(mut empty_group) or { panic('Failed to save empty group: ${err}') } + + retrieved_empty := mydb.group.get(empty_group.id) or { + panic('Failed to get empty group: ${err}') + } + assert retrieved_empty.members.len == 0 + assert retrieved_empty.subgroups.len == 0 + assert retrieved_empty.is_public == true + + // Test group with many members + now := ourtime.now().unix() + mut many_members := []GroupMember{} + for i in 0 .. 100 { + many_members << GroupMember{ + user_id: u32(i + 1) + role: if i % 4 == 0 { + .owner + } else if i % 4 == 1 { + .admin + } else if i % 4 == 2 { + .writer + } else { + .reader + } + joined_at: now + i64(i * 60) // Different join times + } + } + + mut large_group := mydb.group.new( + name: 'Large Group' + description: 'Group with many members' + members: many_members + subgroups: []u32{len: 50, init: u32(index + 1000)} // 50 subgroups + parent_group: 999 + is_public: false + ) or { panic('Failed to create large group: ${err}') } + + mydb.group.set(mut large_group) or { panic('Failed to save large group: ${err}') } + + retrieved_large := mydb.group.get(large_group.id) or { + panic('Failed to get large group: ${err}') + } + assert retrieved_large.members.len == 100 + assert retrieved_large.subgroups.len == 50 + assert retrieved_large.parent_group == 999 + + // Verify member roles are preserved + mut role_counts := map[GroupRole]int{} + for member in retrieved_large.members { + role_counts[member.role]++ + } + assert role_counts[GroupRole.owner] == 25 + assert role_counts[GroupRole.admin] == 25 + assert role_counts[GroupRole.writer] == 25 + assert role_counts[GroupRole.reader] == 25 + + // Test group with empty strings + mut minimal_group := mydb.group.new( + name: '' + description: '' + members: []GroupMember{} + subgroups: []u32{} + parent_group: 0 + is_public: false + ) or { panic('Failed to create minimal group: ${err}') } + + mydb.group.set(mut minimal_group) or { panic('Failed to save minimal group: ${err}') } + + retrieved_minimal := mydb.group.get(minimal_group.id) or { + panic('Failed to get minimal group: ${err}') + } + assert retrieved_minimal.name == '' + assert retrieved_minimal.description == '' + assert retrieved_minimal.members.len == 0 + assert retrieved_minimal.is_public == false +} diff --git a/lib/hero/heromodels/project_issue_test.v b/lib/hero/heromodels/project_issue_test.v new file mode 100644 index 00000000..e769f95b --- /dev/null +++ b/lib/hero/heromodels/project_issue_test.v @@ -0,0 +1,730 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused test + +module heromodels + +import freeflowuniverse.herolib.hero.heromodels { IssuePriority, IssueType, Milestone, Swimlane } +import freeflowuniverse.herolib.data.ourtime + +// Test ProjectIssue model CRUD operations +fn test_project_issue_new() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a project first with default swimlanes + mut project := mydb.project.new( + name: 'Test Project' + description: 'Test project for issues' + start_date: ourtime.now().str() + end_date: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 90)).str() + swimlanes: [ + Swimlane{ + name: 'backlog' + description: 'Backlog items' + order: 1 + color: '#cccccc' + is_done: false + }, + Swimlane{ + name: 'todo' + description: 'To do items' + order: 2 + color: '#ffcccc' + is_done: false + }, + Swimlane{ + name: 'development' + description: 'In development' + order: 3 + color: '#ccffcc' + is_done: false + }, + Swimlane{ + name: 'test' + description: 'Testing' + order: 4 + color: '#ccccff' + is_done: false + }, + Swimlane{ + name: 'completed' + description: 'Completed' + order: 5 + color: '#ccffff' + is_done: true + }, + ] + milestones: [ + Milestone{ + name: 'v1_release' + description: 'Version 1.0 release' + due_date: ourtime.now().unix() + 86400 * 30 + completed: false + issues: [] + }, + Milestone{ + name: 'hotfix_release' + description: 'Hotfix release' + due_date: ourtime.now().unix() + 86400 * 7 + completed: false + issues: [] + }, + Milestone{ + name: 'v2_release' + description: 'Version 2.0 release' + due_date: ourtime.now().unix() + 86400 * 90 + completed: false + issues: [] + }, + Milestone{ + name: 'original_milestone' + description: 'Original milestone' + due_date: ourtime.now().unix() + 86400 * 60 + completed: false + issues: [] + }, + ] + ) or { panic('Failed to create project: ${err}') } + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + project_id := project.id + + // Test creating a new project issue with all fields + now := ourtime.now().unix() + mut issue := mydb.project_issue.new( + name: 'PROJ-123' + description: 'Implement user authentication system' + title: 'User Authentication Feature' + project_id: project_id + issue_type: .story + priority: .high + status: .open + swimlane: 'backlog' + assignees: [u32(10), 20, 30] + reporter: 5 + milestone: 'v1_release' + deadline: ourtime.new_from_epoch(u64(now + 86400 * 14)).str() + estimate: 8 // 8 story points + fs_files: [u32(100), 200] + parent_id: 0 + children: []u32{} + securitypolicy: 1 + tags: ['authentication', 'security', 'backend'] + comments: [] + ) or { panic('Failed to create project issue: ${err}') } + + // Verify the issue was created with correct values + assert issue.name == 'PROJ-123' + assert issue.description == 'Implement user authentication system' + assert issue.title == 'User Authentication Feature' + assert issue.project_id == project_id + assert issue.issue_type == .story + assert issue.priority == .high + assert issue.status == .open + assert issue.swimlane == 'backlog' + assert issue.assignees.len == 3 + assert issue.assignees[0] == 10 + assert issue.reporter == 5 + assert issue.milestone == 'v1_release' + // Allow for small timing differences (within 60 seconds) + expected_deadline := now + 86400 * 14 + assert issue.deadline >= expected_deadline - 60 && issue.deadline <= expected_deadline + 60 + assert issue.estimate == 8 + assert issue.fs_files.len == 2 + assert issue.parent_id == 0 + assert issue.children.len == 0 + assert issue.id == 0 // Should be 0 before saving + assert issue.updated_at > 0 // Should have timestamp +} + +fn test_project_issue_set_and_get() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a project first with default swimlanes + mut project := mydb.project.new( + name: 'Test Project' + description: 'Test project for issues' + start_date: ourtime.now().str() + end_date: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 90)).str() + swimlanes: [ + Swimlane{ + name: 'backlog' + description: 'Backlog items' + order: 1 + color: '#cccccc' + is_done: false + }, + Swimlane{ + name: 'todo' + description: 'To do items' + order: 2 + color: '#ffcccc' + is_done: false + }, + Swimlane{ + name: 'development' + description: 'In development' + order: 3 + color: '#ccffcc' + is_done: false + }, + Swimlane{ + name: 'test' + description: 'Testing' + order: 4 + color: '#ccccff' + is_done: false + }, + Swimlane{ + name: 'completed' + description: 'Completed' + order: 5 + color: '#ccffff' + is_done: true + }, + ] + milestones: [ + Milestone{ + name: 'v1_release' + description: 'Version 1.0 release' + due_date: ourtime.now().unix() + 86400 * 30 + completed: false + issues: [] + }, + Milestone{ + name: 'hotfix_release' + description: 'Hotfix release' + due_date: ourtime.now().unix() + 86400 * 7 + completed: false + issues: [] + }, + Milestone{ + name: 'v2_release' + description: 'Version 2.0 release' + due_date: ourtime.now().unix() + 86400 * 90 + completed: false + issues: [] + }, + Milestone{ + name: 'original_milestone' + description: 'Original milestone' + due_date: ourtime.now().unix() + 86400 * 60 + completed: false + issues: [] + }, + ] + ) or { panic('Failed to create project: ${err}') } + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + project_id := project.id + + // Create a project issue + now := ourtime.now().unix() + mut issue := mydb.project_issue.new( + name: 'BUG-456' + description: 'Fix login page CSS styling issues' + title: 'Login Page Styling Bug' + project_id: project_id + issue_type: .bug + priority: .medium + status: .in_progress + swimlane: 'development' + assignees: [u32(15)] + reporter: 8 + milestone: 'hotfix_release' + deadline: ourtime.new_from_epoch(u64(now + 86400 * 3)).str() // 3 days from now + estimate: 2 // 2 story points + fs_files: [u32(300)] + parent_id: 0 + children: []u32{} + securitypolicy: 2 + tags: ['bug', 'frontend', 'css'] + comments: [] + ) or { panic('Failed to create project issue: ${err}') } + + // Save the issue + mydb.project_issue.set(mut issue) or { panic('Failed to save project issue: ${err}') } + + // Verify ID was assigned + assert issue.id > 0 + original_id := issue.id + + // Retrieve the issue + retrieved_issue := mydb.project_issue.get(issue.id) or { + panic('Failed to get project issue: ${err}') + } + + // Verify all fields match + assert retrieved_issue.id == original_id + assert retrieved_issue.name == 'BUG-456' + assert retrieved_issue.description == 'Fix login page CSS styling issues' + assert retrieved_issue.title == 'Login Page Styling Bug' + assert retrieved_issue.project_id == project_id + assert retrieved_issue.issue_type == .bug + assert retrieved_issue.priority == .medium + assert retrieved_issue.status == .in_progress + assert retrieved_issue.swimlane == 'development' + assert retrieved_issue.assignees.len == 1 + assert retrieved_issue.assignees[0] == 15 + assert retrieved_issue.reporter == 8 + assert retrieved_issue.milestone == 'hotfix_release' + // Allow for small timing differences (within 60 seconds) + expected_deadline_2 := now + 86400 * 3 + assert retrieved_issue.deadline >= expected_deadline_2 - 60 + && retrieved_issue.deadline <= expected_deadline_2 + 60 + assert retrieved_issue.estimate == 2 + assert retrieved_issue.fs_files.len == 1 + assert retrieved_issue.fs_files[0] == 300 + assert retrieved_issue.parent_id == 0 + assert retrieved_issue.children.len == 0 + assert retrieved_issue.created_at > 0 + assert retrieved_issue.updated_at > 0 +} + +fn test_project_issue_types_and_priorities() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a project first with default swimlanes + mut project := mydb.project.new( + name: 'Test Project' + description: 'Test project for issues' + start_date: ourtime.now().str() + end_date: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 90)).str() + swimlanes: [ + Swimlane{ + name: 'backlog' + description: 'Backlog items' + order: 1 + color: '#cccccc' + is_done: false + }, + Swimlane{ + name: 'todo' + description: 'To do items' + order: 2 + color: '#ffcccc' + is_done: false + }, + Swimlane{ + name: 'development' + description: 'In development' + order: 3 + color: '#ccffcc' + is_done: false + }, + Swimlane{ + name: 'test' + description: 'Testing' + order: 4 + color: '#ccccff' + is_done: false + }, + Swimlane{ + name: 'completed' + description: 'Completed' + order: 5 + color: '#ccffff' + is_done: true + }, + ] + milestones: [ + Milestone{ + name: 'v1_release' + description: 'Version 1.0 release' + due_date: ourtime.now().unix() + 86400 * 30 + completed: false + issues: [] + }, + Milestone{ + name: 'hotfix_release' + description: 'Hotfix release' + due_date: ourtime.now().unix() + 86400 * 7 + completed: false + issues: [] + }, + Milestone{ + name: 'v2_release' + description: 'Version 2.0 release' + due_date: ourtime.now().unix() + 86400 * 90 + completed: false + issues: [] + }, + Milestone{ + name: 'original_milestone' + description: 'Original milestone' + due_date: ourtime.now().unix() + 86400 * 60 + completed: false + issues: [] + }, + ] + ) or { panic('Failed to create project: ${err}') } + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + project_id := project.id + + // Test all issue types + issue_types := [IssueType.task, .story, .bug, .question, .epic, .subtask] + priorities := [IssuePriority.lowest, .low, .medium, .high, .highest, .critical] + + for i, issue_type in issue_types { + priority := priorities[i % priorities.len] + + mut issue := mydb.project_issue.new( + name: 'TEST-${i}' + description: 'Testing ${issue_type} with ${priority} priority' + title: 'Test Issue ${i}' + project_id: project_id + issue_type: IssueType(issue_type) + priority: IssuePriority(priority) + status: .open + swimlane: 'development' + assignees: []u32{} + reporter: 1 + milestone: 'v1_release' + deadline: '' + estimate: 1 + fs_files: []u32{} + parent_id: 0 + children: []u32{} + securitypolicy: 1 + tags: ['test'] + comments: [] + ) or { panic('Failed to create issue with type ${issue_type}: ${err}') } + + mydb.project_issue.set(mut issue) or { + panic('Failed to save issue with type ${issue_type}: ${err}') + } + + retrieved_issue := mydb.project_issue.get(issue.id) or { + panic('Failed to get issue with type ${issue_type}: ${err}') + } + assert retrieved_issue.issue_type == IssueType(issue_type) + assert retrieved_issue.priority == IssuePriority(priority) + } +} + +fn test_project_issue_parent_child_relationship() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a project first with default swimlanes + mut project := mydb.project.new( + name: 'Test Project' + description: 'Test project for issues' + start_date: ourtime.now().str() + end_date: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 90)).str() + swimlanes: [ + Swimlane{ + name: 'backlog' + description: 'Backlog items' + order: 1 + color: '#cccccc' + is_done: false + }, + Swimlane{ + name: 'todo' + description: 'To do items' + order: 2 + color: '#ffcccc' + is_done: false + }, + Swimlane{ + name: 'development' + description: 'In development' + order: 3 + color: '#ccffcc' + is_done: false + }, + Swimlane{ + name: 'test' + description: 'Testing' + order: 4 + color: '#ccccff' + is_done: false + }, + Swimlane{ + name: 'completed' + description: 'Completed' + order: 5 + color: '#ccffff' + is_done: true + }, + ] + milestones: [ + Milestone{ + name: 'v1_release' + description: 'Version 1.0 release' + due_date: ourtime.now().unix() + 86400 * 30 + completed: false + issues: [] + }, + Milestone{ + name: 'hotfix_release' + description: 'Hotfix release' + due_date: ourtime.now().unix() + 86400 * 7 + completed: false + issues: [] + }, + Milestone{ + name: 'v2_release' + description: 'Version 2.0 release' + due_date: ourtime.now().unix() + 86400 * 90 + completed: false + issues: [] + }, + Milestone{ + name: 'original_milestone' + description: 'Original milestone' + due_date: ourtime.now().unix() + 86400 * 60 + completed: false + issues: [] + }, + ] + ) or { panic('Failed to create project: ${err}') } + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + project_id := project.id + + // Create parent epic + mut parent_epic := mydb.project_issue.new( + name: 'EPIC-001' + description: 'User management epic' + title: 'User Management System' + project_id: project_id + issue_type: .epic + priority: .high + status: .in_progress + swimlane: 'development' + assignees: [u32(1)] + reporter: 1 + milestone: 'v2_release' + deadline: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 60)).str() + estimate: 50 + fs_files: []u32{} + parent_id: 0 + children: []u32{} + securitypolicy: 1 + tags: ['epic', 'user-management'] + comments: [] + ) or { panic('Failed to create parent epic: ${err}') } + + mydb.project_issue.set(mut parent_epic) or { panic('Failed to save parent epic: ${err}') } + parent_id := parent_epic.id + + // Create child subtasks + mut subtask1 := mydb.project_issue.new( + name: 'TASK-001' + description: 'Create user registration form' + title: 'User Registration Form' + project_id: project_id + issue_type: .subtask + priority: .medium + status: .open + swimlane: 'development' + assignees: [u32(2)] + reporter: 1 + milestone: 'v2_release' + deadline: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 20)).str() + estimate: 5 + fs_files: []u32{} + parent_id: parent_id + children: []u32{} + securitypolicy: 1 + tags: ['subtask', 'frontend'] + comments: [] + ) or { panic('Failed to create subtask1: ${err}') } + + mut subtask2 := mydb.project_issue.new( + name: 'TASK-002' + description: 'Implement user authentication API' + title: 'User Authentication API' + project_id: project_id + issue_type: .subtask + priority: .high + status: .open + swimlane: 'development' + assignees: [u32(3)] + reporter: 1 + milestone: 'v2_release' + deadline: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 25)).str() + estimate: 8 + fs_files: []u32{} + parent_id: parent_id + children: []u32{} + securitypolicy: 1 + tags: ['subtask', 'backend', 'api'] + comments: [] + ) or { panic('Failed to create subtask2: ${err}') } + + mydb.project_issue.set(mut subtask1) or { panic('Failed to save subtask1: ${err}') } + mydb.project_issue.set(mut subtask2) or { panic('Failed to save subtask2: ${err}') } + + // Update parent with children + parent_epic.children = [subtask1.id, subtask2.id] + mydb.project_issue.set(mut parent_epic) or { panic('Failed to update parent epic: ${err}') } + + // Verify relationships + retrieved_parent := mydb.project_issue.get(parent_id) or { + panic('Failed to get parent epic: ${err}') + } + retrieved_subtask1 := mydb.project_issue.get(subtask1.id) or { + panic('Failed to get subtask1: ${err}') + } + retrieved_subtask2 := mydb.project_issue.get(subtask2.id) or { + panic('Failed to get subtask2: ${err}') + } + + assert retrieved_parent.children.len == 2 + assert retrieved_parent.children.contains(subtask1.id) + assert retrieved_parent.children.contains(subtask2.id) + assert retrieved_subtask1.parent_id == parent_id + assert retrieved_subtask2.parent_id == parent_id + assert retrieved_subtask1.issue_type == .subtask + assert retrieved_subtask2.issue_type == .subtask +} + +fn test_project_issue_update() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a project first with default swimlanes + mut project := mydb.project.new( + name: 'Test Project' + description: 'Test project for issues' + start_date: ourtime.now().str() + end_date: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 90)).str() + swimlanes: [ + Swimlane{ + name: 'backlog' + description: 'Backlog items' + order: 1 + color: '#cccccc' + is_done: false + }, + Swimlane{ + name: 'todo' + description: 'To do items' + order: 2 + color: '#ffcccc' + is_done: false + }, + Swimlane{ + name: 'development' + description: 'In development' + order: 3 + color: '#ccffcc' + is_done: false + }, + Swimlane{ + name: 'test' + description: 'Testing' + order: 4 + color: '#ccccff' + is_done: false + }, + Swimlane{ + name: 'completed' + description: 'Completed' + order: 5 + color: '#ccffff' + is_done: true + }, + ] + milestones: [ + Milestone{ + name: 'v1_release' + description: 'Version 1.0 release' + due_date: ourtime.now().unix() + 86400 * 30 + completed: false + issues: [] + }, + Milestone{ + name: 'hotfix_release' + description: 'Hotfix release' + due_date: ourtime.now().unix() + 86400 * 7 + completed: false + issues: [] + }, + Milestone{ + name: 'v2_release' + description: 'Version 2.0 release' + due_date: ourtime.now().unix() + 86400 * 90 + completed: false + issues: [] + }, + Milestone{ + name: 'original_milestone' + description: 'Original milestone' + due_date: ourtime.now().unix() + 86400 * 60 + completed: false + issues: [] + }, + ] + ) or { panic('Failed to create project: ${err}') } + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + project_id := project.id + + // Create and save an issue + now := ourtime.now().unix() + mut issue := mydb.project_issue.new( + name: 'STORY-100' + description: 'Original description' + title: 'Original Title' + project_id: project_id + issue_type: .story + priority: .low + status: .open + swimlane: 'development' + assignees: [u32(1)] + reporter: 2 + milestone: 'original_milestone' + deadline: ourtime.new_from_epoch(u64(now + 86400 * 30)).str() + estimate: 3 + fs_files: []u32{} + parent_id: 0 + children: []u32{} + securitypolicy: 1 + tags: ['original'] + comments: [] + ) or { panic('Failed to create project issue: ${err}') } + + mydb.project_issue.set(mut issue) or { panic('Failed to save project issue: ${err}') } + original_id := issue.id + original_created_at := issue.created_at + original_updated_at := issue.updated_at + + // Update the issue + issue.name = 'STORY-100-UPDATED' + issue.description = 'Updated description' + issue.title = 'Updated Title' + issue.project_id = 2 + issue.issue_type = .task + issue.priority = .critical + issue.status = .done + issue.swimlane = 'completed' + issue.assignees = [u32(5), 6, 7] + issue.reporter = 8 + issue.milestone = 'updated_milestone' + issue.deadline = now + 86400 * 7 + issue.estimate = 13 + issue.fs_files = [u32(400), 500] + + mydb.project_issue.set(mut issue) or { panic('Failed to update project issue: ${err}') } + + // Verify ID remains the same and updated_at is set + assert issue.id == original_id + assert issue.created_at == original_created_at + assert issue.updated_at >= original_updated_at + + // Retrieve and verify updates + updated_issue := mydb.project_issue.get(issue.id) or { + panic('Failed to get updated project issue: ${err}') + } + assert updated_issue.name == 'STORY-100-UPDATED' + assert updated_issue.description == 'Updated description' + assert updated_issue.title == 'Updated Title' + assert updated_issue.project_id == 2 + assert updated_issue.issue_type == .task + assert updated_issue.priority == .critical + assert updated_issue.status == .done + assert updated_issue.swimlane == 'completed' + assert updated_issue.assignees.len == 3 + assert updated_issue.assignees[0] == 5 + assert updated_issue.reporter == 8 + assert updated_issue.milestone == 'updated_milestone' + assert updated_issue.deadline == now + 86400 * 7 + assert updated_issue.estimate == 13 + assert updated_issue.fs_files.len == 2 +} diff --git a/lib/hero/heromodels/project_test.v b/lib/hero/heromodels/project_test.v new file mode 100644 index 00000000..97448bcc --- /dev/null +++ b/lib/hero/heromodels/project_test.v @@ -0,0 +1,617 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused test + +module heromodels + +import freeflowuniverse.herolib.hero.heromodels +import freeflowuniverse.herolib.data.ourtime + +// Test Project model CRUD operations +fn test_project_new() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test creating a new project with all fields + now := ourtime.now().unix() + start_date := ourtime.new_from_epoch(u64(u64(now))).str() + end_date := ourtime.new_from_epoch(u64(u64(now + 86400 * 90))).str() + mut project := mydb.project.new( + name: 'Web Application Redesign' + description: 'Complete redesign of the company website' + swimlanes: [ + Swimlane{ + name: 'backlog' + description: 'Items waiting to be started' + order: 1 + color: '#CCCCCC' + is_done: false + }, + Swimlane{ + name: 'in_progress' + description: 'Currently being worked on' + order: 2 + color: '#FFFF00' + is_done: false + }, + Swimlane{ + name: 'done' + description: 'Completed items' + order: 3 + color: '#00FF00' + is_done: true + }, + ] + milestones: [ + Milestone{ + name: 'design_complete' + description: 'All design mockups completed' + due_date: now + 86400 * 30 // 30 days from now + completed: false + issues: [u32(1), 2, 3] + }, + Milestone{ + name: 'development_complete' + description: 'All development work finished' + due_date: now + 86400 * 60 // 60 days from now + completed: false + issues: [u32(4), 5, 6] + }, + ] + issues: ['issue-1', 'issue-2', 'issue-3'] + fs_files: [u32(100), 200, 300] + status: .planning + start_date: start_date + end_date: end_date + securitypolicy: 1 + tags: ['web', 'redesign', 'frontend'] + comments: [] + ) or { panic('Failed to create project: ${err}') } + + // Verify the project was created with correct values + assert project.name == 'Web Application Redesign' + assert project.description == 'Complete redesign of the company website' + assert project.swimlanes.len == 3 + assert project.swimlanes[0].name == 'backlog' + assert project.swimlanes[1].name == 'in_progress' + assert project.swimlanes[2].name == 'done' + assert project.swimlanes[2].is_done == true + assert project.milestones.len == 2 + assert project.milestones[0].name == 'design_complete' + assert project.milestones[0].issues.len == 3 + assert project.issues.len == 3 + assert project.issues[0] == 'issue-1' + assert project.fs_files.len == 3 + assert project.status == .planning + assert project.start_date > 0 // Should have valid timestamp + assert project.end_date > project.start_date // End should be after start + assert project.id == 0 // Should be 0 before saving + assert project.updated_at > 0 // Should have timestamp +} + +fn test_project_set_and_get() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a project + now := ourtime.now().unix() + start_date := ourtime.new_from_epoch(u64(u64(now - 86400 * 7))).str() + end_date := ourtime.new_from_epoch(u64(u64(now + 86400 * 120))).str() + mut project := mydb.project.new( + name: 'Mobile App Development' + description: 'Native mobile application for iOS and Android' + swimlanes: [ + Swimlane{ + name: 'todo' + description: 'Tasks to be done' + order: 1 + color: '#FF0000' + is_done: false + }, + Swimlane{ + name: 'testing' + description: 'Items being tested' + order: 2 + color: '#0000FF' + is_done: false + }, + ] + milestones: [ + Milestone{ + name: 'mvp_release' + description: 'Minimum viable product release' + due_date: now + 86400 * 45 + completed: false + issues: [u32(10), 11, 12] + }, + ] + issues: ['mobile-1', 'mobile-2'] + fs_files: [u32(500), 600] + status: .active + start_date: start_date + end_date: end_date + securitypolicy: 2 + tags: ['mobile', 'ios', 'android'] + comments: [] + ) or { panic('Failed to create project: ${err}') } + + // Save the project + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + + // Verify ID was assigned + assert project.id > 0 + original_id := project.id + + // Retrieve the project + retrieved_project := mydb.project.get(project.id) or { panic('Failed to get project: ${err}') } + + // Verify all fields match + assert retrieved_project.id == original_id + assert retrieved_project.name == 'Mobile App Development' + assert retrieved_project.description == 'Native mobile application for iOS and Android' + assert retrieved_project.swimlanes.len == 2 + assert retrieved_project.swimlanes[0].name == 'todo' + assert retrieved_project.swimlanes[0].color == '#FF0000' + assert retrieved_project.swimlanes[1].name == 'testing' + assert retrieved_project.swimlanes[1].color == '#0000FF' + assert retrieved_project.milestones.len == 1 + assert retrieved_project.milestones[0].name == 'mvp_release' + assert retrieved_project.milestones[0].issues.len == 3 + assert retrieved_project.issues.len == 2 + assert retrieved_project.issues[0] == 'mobile-1' + assert retrieved_project.fs_files.len == 2 + assert retrieved_project.status == .active + assert retrieved_project.start_date > 0 // Should have valid timestamp + assert retrieved_project.end_date > retrieved_project.start_date // End should be after start + assert retrieved_project.created_at > 0 + assert retrieved_project.updated_at > 0 +} + +fn test_project_status_transitions() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test all project status values + statuses := [heromodels.ProjectStatus.planning, .active, .on_hold, .completed, .cancelled] + now := ourtime.now().unix() + start_date := ourtime.new_from_epoch(u64(u64(now))).str() + end_date := ourtime.new_from_epoch(u64(u64(now + 86400 * 30))).str() + + for status in statuses { + mut project := mydb.project.new( + name: 'Project ${status}' + description: 'Testing status ${status}' + swimlanes: [ + Swimlane{ + name: 'test' + description: 'Test swimlane' + order: 1 + color: '#000000' + is_done: false + }, + ] + milestones: []Milestone{} + issues: []string{} + fs_files: []u32{} + status: heromodels.ProjectStatus(status) + start_date: start_date + end_date: end_date + securitypolicy: 1 + tags: ['test'] + comments: [] + ) or { panic('Failed to create project with status ${status}: ${err}') } + + mydb.project.set(mut project) or { + panic('Failed to save project with status ${status}: ${err}') + } + + retrieved_project := mydb.project.get(project.id) or { + panic('Failed to get project with status ${status}: ${err}') + } + assert retrieved_project.status == heromodels.ProjectStatus(status) + } +} + +fn test_project_swimlanes() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create project with complex swimlanes + mut project := mydb.project.new( + name: 'Swimlane Test Project' + description: 'Testing swimlane functionality' + swimlanes: [ + Swimlane{ + name: 'backlog' + description: 'Product backlog items' + order: 1 + color: '#EEEEEE' + is_done: false + }, + Swimlane{ + name: 'sprint_planning' + description: 'Items being planned for sprint' + order: 2 + color: '#FFCC00' + is_done: false + }, + Swimlane{ + name: 'in_development' + description: 'Currently being developed' + order: 3 + color: '#FF6600' + is_done: false + }, + Swimlane{ + name: 'code_review' + description: 'Under code review' + order: 4 + color: '#3366FF' + is_done: false + }, + Swimlane{ + name: 'testing' + description: 'Being tested' + order: 5 + color: '#9933FF' + is_done: false + }, + Swimlane{ + name: 'done' + description: 'Completed and deployed' + order: 6 + color: '#00CC00' + is_done: true + }, + ] + milestones: []Milestone{} + issues: []string{} + fs_files: []u32{} + status: .active + start_date: ourtime.now().str() + end_date: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 60)).str() + securitypolicy: 1 + tags: ['agile', 'scrum'] + comments: [] + ) or { panic('Failed to create project: ${err}') } + + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + + retrieved_project := mydb.project.get(project.id) or { panic('Failed to get project: ${err}') } + + // Verify all swimlanes are preserved + assert retrieved_project.swimlanes.len == 6 + + // Check specific swimlanes + assert retrieved_project.swimlanes[0].name == 'backlog' + assert retrieved_project.swimlanes[0].order == 1 + assert retrieved_project.swimlanes[0].is_done == false + + assert retrieved_project.swimlanes[5].name == 'done' + assert retrieved_project.swimlanes[5].order == 6 + assert retrieved_project.swimlanes[5].is_done == true + assert retrieved_project.swimlanes[5].color == '#00CC00' + + // Verify order is preserved + for i in 0 .. retrieved_project.swimlanes.len - 1 { + assert retrieved_project.swimlanes[i].order <= retrieved_project.swimlanes[i + 1].order + } +} + +fn test_project_milestones() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create project with multiple milestones + now := ourtime.now().unix() + mut project := mydb.project.new( + name: 'Milestone Test Project' + description: 'Testing milestone functionality' + swimlanes: []Swimlane{} + milestones: [ + Milestone{ + name: 'alpha_release' + description: 'First alpha version' + due_date: now + 86400 * 30 + completed: true + issues: [u32(1), 2, 3, 4, 5] + }, + Milestone{ + name: 'beta_release' + description: 'Beta version with all features' + due_date: now + 86400 * 60 + completed: false + issues: [u32(6), 7, 8, 9, 10, 11, 12] + }, + Milestone{ + name: 'production_release' + description: 'Final production release' + due_date: now + 86400 * 90 + completed: false + issues: [u32(13), 14, 15] + }, + ] + issues: []string{} + fs_files: []u32{} + status: .active + start_date: ourtime.new_from_epoch(u64(now - 86400 * 10)).str() + end_date: ourtime.new_from_epoch(u64(now + 86400 * 100)).str() + securitypolicy: 1 + tags: ['release', 'milestones'] + comments: [] + ) or { panic('Failed to create project: ${err}') } + + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + + retrieved_project := mydb.project.get(project.id) or { panic('Failed to get project: ${err}') } + + // Verify all milestones are preserved + assert retrieved_project.milestones.len == 3 + + // Check specific milestones + assert retrieved_project.milestones[0].name == 'alpha_release' + assert retrieved_project.milestones[0].completed == true + assert retrieved_project.milestones[0].issues.len == 5 + assert retrieved_project.milestones[0].due_date == now + 86400 * 30 + + assert retrieved_project.milestones[1].name == 'beta_release' + assert retrieved_project.milestones[1].completed == false + assert retrieved_project.milestones[1].issues.len == 7 + + assert retrieved_project.milestones[2].name == 'production_release' + assert retrieved_project.milestones[2].completed == false + assert retrieved_project.milestones[2].issues.len == 3 + assert retrieved_project.milestones[2].due_date == now + 86400 * 90 + + // Verify due dates are in chronological order + assert retrieved_project.milestones[0].due_date <= retrieved_project.milestones[1].due_date + assert retrieved_project.milestones[1].due_date <= retrieved_project.milestones[2].due_date +} + +fn test_project_update() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a project + now := ourtime.now().unix() + mut project := mydb.project.new( + name: 'Original Project' + description: 'Original description' + swimlanes: [ + Swimlane{ + name: 'todo' + description: 'To do items' + order: 1 + color: '#FF0000' + is_done: false + }, + ] + milestones: []Milestone{} + issues: ['original-1'] + fs_files: [u32(100)] + status: .planning + start_date: ourtime.new_from_epoch(u64(now)).str() + end_date: ourtime.new_from_epoch(u64(now + 86400 * 30)).str() + securitypolicy: 1 + tags: ['original'] + comments: [] + ) or { panic('Failed to create project: ${err}') } + + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + original_id := project.id + original_created_at := project.created_at + original_updated_at := project.updated_at + + // Update the project + project.name = 'Updated Project' + project.description = 'Updated description' + project.swimlanes = [ + Swimlane{ + name: 'backlog' + description: 'Product backlog' + order: 1 + color: '#CCCCCC' + is_done: false + }, + Swimlane{ + name: 'done' + description: 'Completed items' + order: 2 + color: '#00FF00' + is_done: true + }, + ] + project.milestones = [ + Milestone{ + name: 'v1_release' + description: 'Version 1.0 release' + due_date: now + 86400 * 60 + completed: false + issues: [u32(1), 2, 3] + }, + ] + project.issues = ['updated-1', 'updated-2'] + project.fs_files = [u32(200), 300] + project.status = .active + project.end_date = now + 86400 * 60 + + mydb.project.set(mut project) or { panic('Failed to update project: ${err}') } + + // Verify ID remains the same and updated_at is set + assert project.id == original_id + assert project.created_at == original_created_at + assert project.updated_at >= original_updated_at + + // Retrieve and verify updates + updated_project := mydb.project.get(project.id) or { + panic('Failed to get updated project: ${err}') + } + assert updated_project.name == 'Updated Project' + assert updated_project.description == 'Updated description' + assert updated_project.swimlanes.len == 2 + assert updated_project.swimlanes[0].name == 'backlog' + assert updated_project.swimlanes[1].name == 'done' + assert updated_project.swimlanes[1].is_done == true + assert updated_project.milestones.len == 1 + assert updated_project.milestones[0].name == 'v1_release' + assert updated_project.issues.len == 2 + assert updated_project.issues[0] == 'updated-1' + assert updated_project.fs_files.len == 2 + assert updated_project.status == .active +} + +fn test_project_exist() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test non-existent project + exists := mydb.project.exist(999) or { panic('Failed to check existence: ${err}') } + assert exists == false + + // Create and save a project + mut project := mydb.project.new( + name: 'Existence Test' + description: 'Testing existence' + swimlanes: []Swimlane{} + milestones: []Milestone{} + issues: []string{} + fs_files: []u32{} + status: .planning + start_date: ourtime.now().str() + end_date: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 30)).str() + securitypolicy: 1 + tags: ['test'] + comments: [] + ) or { panic('Failed to create project: ${err}') } + + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + + // Test existing project + exists_after_save := mydb.project.exist(project.id) or { + panic('Failed to check existence: ${err}') + } + assert exists_after_save == true +} + +fn test_project_delete() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a project + mut project := mydb.project.new( + name: 'To Be Deleted' + description: 'This project will be deleted' + swimlanes: []Swimlane{} + milestones: []Milestone{} + issues: []string{} + fs_files: []u32{} + status: .cancelled + start_date: ourtime.now().str() + end_date: ourtime.new_from_epoch(u64(ourtime.now().unix() + 86400 * 30)).str() + securitypolicy: 1 + tags: [] + comments: [] + ) or { panic('Failed to create project: ${err}') } + + mydb.project.set(mut project) or { panic('Failed to save project: ${err}') } + project_id := project.id + + // Verify it exists + exists_before := mydb.project.exist(project_id) or { + panic('Failed to check existence: ${err}') + } + assert exists_before == true + + // Delete the project + mydb.project.delete(project_id) or { panic('Failed to delete project: ${err}') } + + // Verify it no longer exists + exists_after := mydb.project.exist(project_id) or { panic('Failed to check existence: ${err}') } + assert exists_after == false + + // Verify get fails + if _ := mydb.project.get(project_id) { + panic('Should not be able to get deleted project') + } +} + +fn test_project_list() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Clear any existing projects by creating a fresh DB + mydb = heromodels.new() or { panic('Failed to create fresh DB: ${err}') } + + // Initially should be empty + initial_list := mydb.project.list() or { panic('Failed to list projects: ${err}') } + initial_count := initial_list.len + + // Create multiple projects + now := ourtime.now().unix() + mut project1 := mydb.project.new( + name: 'Project Alpha' + description: 'First test project' + swimlanes: [ + Swimlane{ + name: 'todo' + description: 'To do' + order: 1 + color: '#FF0000' + is_done: false + }, + ] + milestones: []Milestone{} + issues: ['alpha-1'] + fs_files: []u32{} + status: .active + start_date: ourtime.new_from_epoch(u64(now)).str() + end_date: ourtime.new_from_epoch(u64(now + 86400 * 30)).str() + securitypolicy: 1 + tags: ['alpha', 'test'] + comments: [] + ) or { panic('Failed to create project1: ${err}') } + + mut project2 := mydb.project.new( + name: 'Project Beta' + description: 'Second test project' + swimlanes: []Swimlane{} + milestones: [ + Milestone{ + name: 'beta_milestone' + description: 'Beta milestone' + due_date: now + 86400 * 45 + completed: false + issues: [u32(1), 2] + }, + ] + issues: ['beta-1', 'beta-2'] + fs_files: [u32(100), 200] + status: .planning + start_date: ourtime.new_from_epoch(u64(now + 86400 * 7)).str() + end_date: ourtime.new_from_epoch(u64(now + 86400 * 60)).str() + securitypolicy: 2 + tags: ['beta', 'test'] + comments: [] + ) or { panic('Failed to create project2: ${err}') } + + // Save both projects + mydb.project.set(mut project1) or { panic('Failed to save project1: ${err}') } + mydb.project.set(mut project2) or { panic('Failed to save project2: ${err}') } + + // List projects + project_list := mydb.project.list() or { panic('Failed to list projects: ${err}') } + + // Should have 2 more projects than initially + assert project_list.len == initial_count + 2 + + // Find our projects in the list + mut found_project1 := false + mut found_project2 := false + + for proj in project_list { + if proj.name == 'Project Alpha' { + found_project1 = true + assert proj.status == .active + assert proj.swimlanes.len == 1 + assert proj.issues.len == 1 + } + if proj.name == 'Project Beta' { + found_project2 = true + assert proj.status == .planning + assert proj.milestones.len == 1 + assert proj.issues.len == 2 + } + } + + assert found_project1 == true + assert found_project2 == true +} diff --git a/lib/hero/heromodels/user_test.v b/lib/hero/heromodels/user_test.v new file mode 100644 index 00000000..28d6da44 --- /dev/null +++ b/lib/hero/heromodels/user_test.v @@ -0,0 +1,370 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused test + +module heromodels + +import freeflowuniverse.herolib.hero.heromodels + +// Test User model CRUD operations +fn test_user_new() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test creating a new user with all fields + mut user := mydb.user.new( + name: 'John Doe' + description: 'Software developer and tech enthusiast' + email: 'john.doe@example.com' + public_key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ...' + phone: '+1-555-123-4567' + address: '123 Main St, Anytown, USA' + avatar_url: 'https://example.com/avatar.jpg' + bio: 'Passionate about technology and open source' + timezone: 'America/New_York' + status: .active + securitypolicy: 1 + tags: 1 + comments: []u32{} + ) or { panic('Failed to create user: ${err}') } + + // Verify the user was created with correct values + assert user.name == 'John Doe' + assert user.description == 'Software developer and tech enthusiast' + assert user.email == 'john.doe@example.com' + assert user.public_key == 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ...' + assert user.phone == '+1-555-123-4567' + assert user.address == '123 Main St, Anytown, USA' + assert user.avatar_url == 'https://example.com/avatar.jpg' + assert user.bio == 'Passionate about technology and open source' + assert user.timezone == 'America/New_York' + assert user.status == .active + assert user.id == 0 // Should be 0 before saving + assert user.updated_at > 0 // Should have timestamp +} + +fn test_user_set_and_get() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create a user + mut user := mydb.user.new( + name: 'Alice Smith' + description: 'Product manager with 5 years experience' + email: 'alice.smith@company.com' + public_key: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...' + phone: '+44-20-7946-0958' + address: 'London, UK' + avatar_url: 'https://company.com/avatars/alice.png' + bio: 'Leading product development teams' + timezone: 'Europe/London' + status: .active + securitypolicy: 2 + tags: 2 + comments: []u32{} + ) or { panic('Failed to create user: ${err}') } + + // Save the user + mydb.user.set(mut user) or { panic('Failed to save user: ${err}') } + + // Verify ID was assigned + assert user.id > 0 + original_id := user.id + + // Retrieve the user + retrieved_user := mydb.user.get(user.id) or { panic('Failed to get user: ${err}') } + + // Verify all fields match + assert retrieved_user.id == original_id + assert retrieved_user.name == 'Alice Smith' + assert retrieved_user.description == 'Product manager with 5 years experience' + assert retrieved_user.email == 'alice.smith@company.com' + assert retrieved_user.public_key == 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...' + assert retrieved_user.phone == '+44-20-7946-0958' + assert retrieved_user.address == 'London, UK' + assert retrieved_user.avatar_url == 'https://company.com/avatars/alice.png' + assert retrieved_user.bio == 'Leading product development teams' + assert retrieved_user.timezone == 'Europe/London' + assert retrieved_user.status == .active + assert retrieved_user.created_at > 0 + assert retrieved_user.updated_at > 0 +} + +fn test_user_update() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a user + mut user := mydb.user.new( + name: 'Bob Wilson' + description: 'Junior developer' + email: 'bob.wilson@startup.com' + public_key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQ...' + phone: '+1-415-555-0123' + address: 'San Francisco, CA' + avatar_url: 'https://startup.com/bob.jpg' + bio: 'Learning and growing in tech' + timezone: 'America/Los_Angeles' + status: .pending + securitypolicy: 1 + tags: 3 + comments: []u32{} + ) or { panic('Failed to create user: ${err}') } + + mydb.user.set(mut user) or { panic('Failed to save user: ${err}') } + original_id := user.id + original_created_at := user.created_at + original_updated_at := user.updated_at + + // Update the user + user.name = 'Bob Wilson Jr.' + user.description = 'Senior developer' + user.email = 'bob.wilson.jr@bigtech.com' + user.phone = '+1-415-555-9999' + user.address = 'Palo Alto, CA' + user.bio = 'Experienced full-stack developer' + user.status = .active + + mydb.user.set(mut user) or { panic('Failed to update user: ${err}') } + + // Verify ID remains the same and updated_at is set + assert user.id == original_id + assert user.created_at == original_created_at + assert user.updated_at >= original_updated_at + + // Retrieve and verify updates + updated_user := mydb.user.get(user.id) or { panic('Failed to get updated user: ${err}') } + assert updated_user.name == 'Bob Wilson Jr.' + assert updated_user.description == 'Senior developer' + assert updated_user.email == 'bob.wilson.jr@bigtech.com' + assert updated_user.phone == '+1-415-555-9999' + assert updated_user.address == 'Palo Alto, CA' + assert updated_user.bio == 'Experienced full-stack developer' + assert updated_user.status == .active +} + +fn test_user_status_transitions() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test all user status values + statuses := [heromodels.UserStatus.pending, .active, .inactive, .suspended] + + for status in statuses { + mut user := mydb.user.new( + name: 'Test User ${status}' + description: 'Testing status ${status}' + email: 'test.${status}@example.com' + public_key: '' + phone: '' + address: '' + avatar_url: '' + bio: '' + timezone: 'UTC' + status: status + securitypolicy: 1 + tags: 0 + comments: []u32{} + ) or { panic('Failed to create user with status ${status}: ${err}') } + + mydb.user.set(mut user) or { panic('Failed to save user with status ${status}: ${err}') } + + retrieved_user := mydb.user.get(user.id) or { + panic('Failed to get user with status ${status}: ${err}') + } + assert retrieved_user.status == status + } +} + +fn test_user_exist() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test non-existent user + exists := mydb.user.exist(999) or { panic('Failed to check existence: ${err}') } + assert exists == false + + // Create and save a user + mut user := mydb.user.new( + name: 'Existence Test' + description: 'Testing existence' + email: 'exist@test.com' + public_key: '' + phone: '' + address: '' + avatar_url: '' + bio: '' + timezone: 'UTC' + status: .active + securitypolicy: 1 + tags: 4 + comments: []u32{} + ) or { panic('Failed to create user: ${err}') } + + mydb.user.set(mut user) or { panic('Failed to save user: ${err}') } + + // Test existing user + exists_after_save := mydb.user.exist(user.id) or { panic('Failed to check existence: ${err}') } + assert exists_after_save == true +} + +fn test_user_delete() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Create and save a user + mut user := mydb.user.new( + name: 'To Be Deleted' + description: 'This user will be deleted' + email: 'delete@me.com' + public_key: '' + phone: '' + address: '' + avatar_url: '' + bio: '' + timezone: 'UTC' + status: .inactive + securitypolicy: 1 + tags: 0 + comments: []u32{} + ) or { panic('Failed to create user: ${err}') } + + mydb.user.set(mut user) or { panic('Failed to save user: ${err}') } + user_id := user.id + + // Verify it exists + exists_before := mydb.user.exist(user_id) or { panic('Failed to check existence: ${err}') } + assert exists_before == true + + // Delete the user + mydb.user.delete(user_id) or { panic('Failed to delete user: ${err}') } + + // Verify it no longer exists + exists_after := mydb.user.exist(user_id) or { panic('Failed to check existence: ${err}') } + assert exists_after == false + + // Verify get fails + if _ := mydb.user.get(user_id) { + panic('Should not be able to get deleted user') + } +} + +fn test_user_list() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Clear any existing users by creating a fresh DB + mydb = heromodels.new() or { panic('Failed to create fresh DB: ${err}') } + + // Initially should be empty + initial_list := mydb.user.list() or { panic('Failed to list users: ${err}') } + initial_count := initial_list.len + + // Create multiple users + mut user1 := mydb.user.new( + name: 'User One' + description: 'First test user' + email: 'user1@test.com' + public_key: 'key1' + phone: '+1-111-111-1111' + address: 'Address 1' + avatar_url: 'avatar1.jpg' + bio: 'Bio 1' + timezone: 'UTC' + status: .active + securitypolicy: 1 + tags: 5 + comments: []u32{} + ) or { panic('Failed to create user1: ${err}') } + + mut user2 := mydb.user.new( + name: 'User Two' + description: 'Second test user' + email: 'user2@test.com' + public_key: 'key2' + phone: '+1-222-222-2222' + address: 'Address 2' + avatar_url: 'avatar2.jpg' + bio: 'Bio 2' + timezone: 'America/New_York' + status: .pending + securitypolicy: 2 + tags: 6 + comments: []u32{} + ) or { panic('Failed to create user2: ${err}') } + + // Save both users + mydb.user.set(mut user1) or { panic('Failed to save user1: ${err}') } + mydb.user.set(mut user2) or { panic('Failed to save user2: ${err}') } + + // List users + user_list := mydb.user.list() or { panic('Failed to list users: ${err}') } + + // Should have 2 more users than initially + assert user_list.len == initial_count + 2 + + // Find our users in the list + mut found_user1 := false + mut found_user2 := false + + for usr in user_list { + if usr.name == 'User One' { + found_user1 = true + assert usr.email == 'user1@test.com' + assert usr.status == .active + } + if usr.name == 'User Two' { + found_user2 = true + assert usr.email == 'user2@test.com' + assert usr.status == .pending + } + } + + assert found_user1 == true + assert found_user2 == true +} + +fn test_user_edge_cases() { + mut mydb := heromodels.new() or { panic('Failed to create DB: ${err}') } + + // Test user with empty fields + mut empty_user := mydb.user.new( + name: '' + description: '' + email: '' + public_key: '' + phone: '' + address: '' + avatar_url: '' + bio: '' + timezone: '' + status: .inactive + securitypolicy: 0 + tags: 0 + comments: []u32{} + ) or { panic('Failed to create empty user: ${err}') } + + mydb.user.set(mut empty_user) or { panic('Failed to save empty user: ${err}') } + + retrieved_empty := mydb.user.get(empty_user.id) or { panic('Failed to get empty user: ${err}') } + assert retrieved_empty.name == '' + assert retrieved_empty.email == '' + assert retrieved_empty.phone == '' + assert retrieved_empty.status == .inactive + + // Test user with very long fields + long_text := 'Very long text. '.repeat(100) + mut long_user := mydb.user.new( + name: long_text + description: long_text + email: 'very.long.email.address.that.might.be.too.long@example.com' + public_key: long_text + phone: '+1-555-' + '1234567890'.repeat(10) + address: long_text + avatar_url: 'https://example.com/' + 'path/'.repeat(50) + 'avatar.jpg' + bio: long_text + timezone: 'America/New_York' + status: .active + securitypolicy: 999 + tags: 7 + comments: []u32{} + ) or { panic('Failed to create long user: ${err}') } + + mydb.user.set(mut long_user) or { panic('Failed to save long user: ${err}') } + + retrieved_long := mydb.user.get(long_user.id) or { panic('Failed to get long user: ${err}') } + assert retrieved_long.name == long_text + assert retrieved_long.description == long_text + assert retrieved_long.status == .active +}