test: add comprehensive CRUD and edge case tests for heromodels
- Add tests for CalendarEvent, Calendar, ChatGroup, and ChatMessage models - Include tests for Comment, Group, Project, ProjectIssue, and User models - Cover create, read, update, delete, existence, and list operations - Validate model-specific features like recurrence, chat types, group roles - Test edge cases for various fields, including empty and large values
This commit is contained in:
537
lib/hero/heromodels/calendar_event_test.v
Normal file
537
lib/hero/heromodels/calendar_event_test.v
Normal file
@@ -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
|
||||
}
|
||||
289
lib/hero/heromodels/calendar_test.v
Normal file
289
lib/hero/heromodels/calendar_test.v
Normal file
@@ -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
|
||||
}
|
||||
362
lib/hero/heromodels/chat_group_test.v
Normal file
362
lib/hero/heromodels/chat_group_test.v
Normal file
@@ -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
|
||||
}
|
||||
396
lib/hero/heromodels/chat_message_test.v
Normal file
396
lib/hero/heromodels/chat_message_test.v
Normal file
@@ -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
|
||||
}
|
||||
276
lib/hero/heromodels/comment_test.v
Normal file
276
lib/hero/heromodels/comment_test.v
Normal file
@@ -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
|
||||
}
|
||||
517
lib/hero/heromodels/group_test.v
Normal file
517
lib/hero/heromodels/group_test.v
Normal file
@@ -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
|
||||
}
|
||||
730
lib/hero/heromodels/project_issue_test.v
Normal file
730
lib/hero/heromodels/project_issue_test.v
Normal file
@@ -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
|
||||
}
|
||||
617
lib/hero/heromodels/project_test.v
Normal file
617
lib/hero/heromodels/project_test.v
Normal file
@@ -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
|
||||
}
|
||||
370
lib/hero/heromodels/user_test.v
Normal file
370
lib/hero/heromodels/user_test.v
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user