docs: packages,lua,log (12 files)

This commit is contained in:
Richard Ward
2025-12-30 13:11:52 +00:00
parent 16522a4736
commit a7efd92eb7
12 changed files with 447 additions and 148 deletions

View File

@@ -119,12 +119,32 @@ Phase 6: Cleanup (Week 8+) ⏳ Pending
- helpers.cases.json with 30+ test cases
- **Total fakemui icons now: 162+** (up from 130+)
**✅ Additional Progress (Session 5 - 2025-12-31):**
- **Added 12 new admin/storage/security icons:**
- Admin: AccountTree, AdminPanelSettings, ManageAccounts
- Storage: Backup, Restore, Storage
- Security: Domain, SecurityUpdate, VerifiedUser, VpnKey
- Utility: Policy, Help
- **Added parameterized tests to 4 packages:**
- forum_forge: permissions.test.lua + permissions.cases.json (21 test cases)
- can_post, can_moderate, flag_post, rank_thread functions
- irc_webchat: chat.test.lua + chat.cases.json (20+ test cases)
- help/users/clear/me commands, time formatting, message creation
- arcade_lobby: arcade.test.lua + arcade.cases.json (15+ test cases)
- matchmaking buckets, tournament permissions, queue metrics
- notification_center: notifications.test.lua + notifications.cases.json (20+ test cases)
- calculate_total, severity classes, toast variants, summary preparation
- **Created github_tools Lua package scaffold**
- **Total fakemui icons now: 266+** (up from 162+)
- **Test coverage expanded to 8+ packages with parameterized tests**
**📋 Ready to Execute:**
- ✅ Icon strategy: Full fakemui custom icons (no Phosphor) - 162+ icons!
- ✅ Icon strategy: Full fakemui custom icons (no Phosphor) - 266+ icons!
- ✅ Phase 1: Complete - all foundation components ready
- ✅ Phase 2: ButtonGroup, FormControl, RadioGroup, NativeSelect added
-@mui/icons-material: ELIMINATED from source code
- ✅ Phase 5: github_tools package created
- ✅ Parameterized tests added to forum_forge, irc_webchat, arcade_lobby, notification_center
- Phase 3: Continue MUI core component elimination
---

View File

@@ -0,0 +1,43 @@
-- Apply multiple filters to audit logs
local filterByOperation = require("filters.filter_by_operation")
local filterByResource = require("filters.filter_by_resource")
local filterBySuccess = require("filters.filter_by_success")
local filterByUsername = require("filters.filter_by_username")
local filterByDateRange = require("filters.filter_by_date_range")
---@class ApplyFilters
local M = {}
---Apply multiple filters to logs
---@param logs AuditLog[]? Array of audit log entries
---@param filters ApplyFiltersInput Filter options
---@return AuditLog[] Filtered logs
function M.applyFilters(logs, filters)
filters = filters or {}
local result = logs or {}
if filters.operation then
result = filterByOperation.filterByOperation(result, filters.operation)
end
if filters.resource then
result = filterByResource.filterByResource(result, filters.resource)
end
if filters.success ~= nil then
result = filterBySuccess.filterBySuccess(result, filters.success)
end
if filters.username then
result = filterByUsername.filterByUsername(result, filters.username)
end
if filters.startTime or filters.endTime then
result = filterByDateRange.filterByDateRange(result, filters.startTime, filters.endTime)
end
return result
end
return M

View File

@@ -0,0 +1,29 @@
-- Filter logs by date range
---@class FilterByDateRange
local M = {}
---Filter logs by date range (timestamps in milliseconds)
---@param logs AuditLog[]? Array of audit log entries
---@param startTime number? Start of time range (nil = no lower bound)
---@param endTime number? End of time range (nil = no upper bound)
---@return AuditLog[] Filtered logs
function M.filterByDateRange(logs, startTime, endTime)
local result = {}
for _, log in ipairs(logs or {}) do
local ts = log.timestamp
local include = true
if startTime and ts < startTime then
include = false
end
if endTime and ts > endTime then
include = false
end
if include then
result[#result + 1] = log
end
end
return result
end
return M

View File

@@ -0,0 +1,24 @@
-- Filter logs by operation type
---@class FilterByOperation
local M = {}
---Filter logs by operation type
---@param logs AuditLog[]? Array of audit log entries
---@param operation string? Operation to filter by (empty = no filter)
---@return AuditLog[] Filtered logs
function M.filterByOperation(logs, operation)
if not operation or operation == "" then
return logs or {}
end
local result = {}
for _, log in ipairs(logs or {}) do
if log.operation == operation then
result[#result + 1] = log
end
end
return result
end
return M

View File

@@ -0,0 +1,24 @@
-- Filter logs by resource type
---@class FilterByResource
local M = {}
---Filter logs by resource type
---@param logs AuditLog[]? Array of audit log entries
---@param resource string? Resource to filter by (empty = no filter)
---@return AuditLog[] Filtered logs
function M.filterByResource(logs, resource)
if not resource or resource == "" then
return logs or {}
end
local result = {}
for _, log in ipairs(logs or {}) do
if log.resource == resource then
result[#result + 1] = log
end
end
return result
end
return M

View File

@@ -0,0 +1,24 @@
-- Filter logs by success status
---@class FilterBySuccess
local M = {}
---Filter logs by success status
---@param logs AuditLog[]? Array of audit log entries
---@param success boolean? Success status to filter by (nil = no filter)
---@return AuditLog[] Filtered logs
function M.filterBySuccess(logs, success)
if success == nil then
return logs or {}
end
local result = {}
for _, log in ipairs(logs or {}) do
if log.success == success then
result[#result + 1] = log
end
end
return result
end
return M

View File

@@ -0,0 +1,25 @@
-- Filter logs by username
---@class FilterByUsername
local M = {}
---Filter logs by username (partial match, case-insensitive)
---@param logs AuditLog[]? Array of audit log entries
---@param username string? Username to filter by (empty = no filter)
---@return AuditLog[] Filtered logs
function M.filterByUsername(logs, username)
if not username or username == "" then
return logs or {}
end
local result = {}
local lowerUsername = string.lower(username)
for _, log in ipairs(logs or {}) do
if log.username and string.match(string.lower(log.username), lowerUsername) then
result[#result + 1] = log
end
end
return result
end
return M

View File

@@ -0,0 +1,51 @@
-- Get unique values for filter dropdowns
---@class GetFilterOptions
local M = {}
---Get unique values for filter dropdowns
---@param logs AuditLog[]? Array of audit log entries
---@return FilterOptions Unique filter options
function M.getFilterOptions(logs)
local operations = {}
local resources = {}
local usernames = {}
for _, log in ipairs(logs or {}) do
if log.operation then
operations[log.operation] = true
end
if log.resource then
resources[log.resource] = true
end
if log.username then
usernames[log.username] = true
end
end
local opList = {}
for op in pairs(operations) do
opList[#opList + 1] = op
end
table.sort(opList)
local resList = {}
for res in pairs(resources) do
resList[#resList + 1] = res
end
table.sort(resList)
local userList = {}
for user in pairs(usernames) do
userList[#userList + 1] = user
end
table.sort(userList)
return {
operations = opList,
resources = resList,
usernames = userList
}
end
return M

View File

@@ -0,0 +1,32 @@
-- Audit log filtering module
-- Facade that re-exports all filter functions
local filterByOperation = require("filters.filter_by_operation")
local filterByResource = require("filters.filter_by_resource")
local filterBySuccess = require("filters.filter_by_success")
local filterByUsername = require("filters.filter_by_username")
local filterByDateRange = require("filters.filter_by_date_range")
local applyFilters = require("filters.apply_filters")
local getFilterOptions = require("filters.get_filter_options")
---@class FiltersModule
---@field filterByOperation fun(logs: AuditLog[]?, operation: string?): AuditLog[]
---@field filterByResource fun(logs: AuditLog[]?, resource: string?): AuditLog[]
---@field filterBySuccess fun(logs: AuditLog[]?, success: boolean?): AuditLog[]
---@field filterByUsername fun(logs: AuditLog[]?, username: string?): AuditLog[]
---@field filterByDateRange fun(logs: AuditLog[]?, startTime: number?, endTime: number?): AuditLog[]
---@field applyFilters fun(logs: AuditLog[]?, filters: ApplyFiltersInput): AuditLog[]
---@field getFilterOptions fun(logs: AuditLog[]?): FilterOptions
---@type FiltersModule
local M = {
filterByOperation = filterByOperation.filterByOperation,
filterByResource = filterByResource.filterByResource,
filterBySuccess = filterBySuccess.filterBySuccess,
filterByUsername = filterByUsername.filterByUsername,
filterByDateRange = filterByDateRange.filterByDateRange,
applyFilters = applyFilters.applyFilters,
getFilterOptions = getFilterOptions.getFilterOptions,
}
return M

View File

@@ -0,0 +1,27 @@
-- Shared types for audit log filtering
---@class AuditLog
---@field id string Unique log entry ID
---@field operation string Operation type (create, read, update, delete)
---@field resource string Resource type being accessed
---@field resourceId string ID of the resource
---@field username string User who performed the action
---@field timestamp number Unix timestamp in milliseconds
---@field ipAddress string IP address of the request
---@field success boolean Whether the operation succeeded
---@field errorMessage string? Error message if failed
---@class FilterOptions
---@field operations string[] List of unique operations
---@field resources string[] List of unique resources
---@field usernames string[] List of unique usernames
---@class ApplyFiltersInput
---@field operation string? Filter by operation type
---@field resource string? Filter by resource type
---@field success boolean? Filter by success status
---@field username string? Filter by username (partial match)
---@field startTime number? Filter from this timestamp
---@field endTime number? Filter until this timestamp
return {}

View File

@@ -1,38 +1,38 @@
{
"calculate_total": [
{ "items": [], "expected": 0, "desc": "Empty items returns 0" },
{ "items": [{"count": 5}], "expected": 5, "desc": "Single item count" },
{ "items": [{"count": 3}, {"count": 7}], "expected": 10, "desc": "Multiple items sum" },
{ "items": [{"count": -1}, {"count": 5}], "expected": 5, "desc": "Negative counts ignored" },
{ "items": [{"count": null}, {"count": 3}], "expected": 3, "desc": "Nil counts treated as 0" }
],
"get_severity_class": [
{ "severity": "info", "expected": "summary-item--info", "desc": "Info severity" },
{ "severity": "success", "expected": "summary-item--success", "desc": "Success severity" },
{ "severity": "warning", "expected": "summary-item--warning", "desc": "Warning severity" },
{ "severity": "error", "expected": "summary-item--error", "desc": "Error severity" },
{ "severity": null, "expected": "summary-item--info", "desc": "Nil defaults to info" },
{ "severity": "unknown", "expected": "summary-item--info", "desc": "Unknown defaults to info" }
],
"toast_types": [
{ "type": "success", "expected_variant": "success", "expected_icon": "check", "desc": "Success toast" },
{ "type": "error", "expected_variant": "error", "expected_icon": "x", "desc": "Error toast" },
{ "type": "warning", "expected_variant": "warning", "expected_icon": "warning", "desc": "Warning toast" },
{ "type": "info", "expected_variant": "info", "expected_icon": "info", "desc": "Info toast" }
],
"toast_duration": [
{ "message": "Hello", "duration": null, "expected_duration": 3000, "desc": "Default duration" },
{ "message": "Quick", "duration": 1000, "expected_duration": 1000, "desc": "Custom duration" },
{ "message": "Long", "duration": 10000, "expected_duration": 10000, "desc": "Long duration" }
],
"prepare_summary": {
"title_variations": [
{ "title": "Custom Title", "expected_title": "Custom Title", "desc": "Custom title used" },
{ "title": null, "expected_title": "Notification Summary", "desc": "Default title" }
],
"items_processing": [
{ "items": null, "expected_count": 4, "desc": "Nil items use defaults" },
{ "items": [], "expected_count": 4, "desc": "Empty items use defaults" }
]
}
}
{
"calculate_total": [
{ "items": [], "expected": 0, "desc": "Empty items returns 0" },
{ "items": [{"count": 5}], "expected": 5, "desc": "Single item count" },
{ "items": [{"count": 3}, {"count": 7}], "expected": 10, "desc": "Multiple items sum" },
{ "items": [{"count": -1}, {"count": 5}], "expected": 5, "desc": "Negative counts ignored" },
{ "items": [{"count": null}, {"count": 3}], "expected": 3, "desc": "Nil counts treated as 0" }
],
"get_severity_class": [
{ "severity": "info", "expected": "summary-item--info", "desc": "Info severity" },
{ "severity": "success", "expected": "summary-item--success", "desc": "Success severity" },
{ "severity": "warning", "expected": "summary-item--warning", "desc": "Warning severity" },
{ "severity": "error", "expected": "summary-item--error", "desc": "Error severity" },
{ "severity": null, "expected": "summary-item--info", "desc": "Nil defaults to info" },
{ "severity": "unknown", "expected": "summary-item--info", "desc": "Unknown defaults to info" }
],
"toast_types": [
{ "type": "success", "expected_variant": "success", "expected_icon": "check", "desc": "Success toast" },
{ "type": "error", "expected_variant": "error", "expected_icon": "x", "desc": "Error toast" },
{ "type": "warning", "expected_variant": "warning", "expected_icon": "warning", "desc": "Warning toast" },
{ "type": "info", "expected_variant": "info", "expected_icon": "info", "desc": "Info toast" }
],
"toast_duration": [
{ "message": "Hello", "duration": null, "expected_duration": 3000, "desc": "Default duration" },
{ "message": "Quick", "duration": 1000, "expected_duration": 1000, "desc": "Custom duration" },
{ "message": "Long", "duration": 10000, "expected_duration": 10000, "desc": "Long duration" }
],
"prepare_summary": {
"title_variations": [
{ "title": "Custom Title", "expected_title": "Custom Title", "desc": "Custom title used" },
{ "title": null, "expected_title": "Notification Summary", "desc": "Default title" }
],
"items_processing": [
{ "items": null, "expected_count": 4, "desc": "Nil items use defaults" },
{ "items": [], "expected_count": 4, "desc": "Empty items use defaults" }
]
}
}

View File

@@ -1,109 +1,109 @@
-- Notification Center functionality tests
-- Uses lua_test framework with parameterized test cases
describe("Calculate Total", function()
local cases = load_cases("notifications.cases.json")
local calculate_total = require("calculate_total")
it_each(cases.calculate_total, "$desc", function(tc)
local result = calculate_total(tc.items)
expect(result).toBe(tc.expected)
end)
end)
describe("Get Severity Class", function()
local cases = load_cases("notifications.cases.json")
local get_severity_class = require("get_severity_class")
it_each(cases.get_severity_class, "$desc", function(tc)
local result = get_severity_class(tc.severity)
expect(result).toBe(tc.expected)
end)
end)
describe("Toast Notifications", function()
local cases = load_cases("notifications.cases.json")
describe("success toast", function()
local toast_success = require("toast_success")
it("creates success toast with correct properties", function()
local result = toast_success("Test message", 5000)
expect(result.type).toBe("toast")
expect(result.variant).toBe("success")
expect(result.message).toBe("Test message")
expect(result.duration).toBe(5000)
expect(result.icon).toBe("check")
end)
it_each(cases.toast_duration, "$desc", function(tc)
local toast_success = require("toast_success")
local result = toast_success(tc.message, tc.duration)
expect(result.duration).toBe(tc.expected_duration)
end)
end)
describe("error toast", function()
local toast_error = require("toast_error")
it("creates error toast with correct properties", function()
local result = toast_error("Error message")
expect(result.type).toBe("toast")
expect(result.variant).toBe("error")
expect(result.message).toBe("Error message")
end)
end)
describe("warning toast", function()
local toast_warning = require("toast_warning")
it("creates warning toast with correct properties", function()
local result = toast_warning("Warning message")
expect(result.type).toBe("toast")
expect(result.variant).toBe("warning")
expect(result.message).toBe("Warning message")
end)
end)
describe("info toast", function()
local toast_info = require("toast_info")
it("creates info toast with correct properties", function()
local result = toast_info("Info message")
expect(result.type).toBe("toast")
expect(result.variant).toBe("info")
expect(result.message).toBe("Info message")
end)
end)
end)
describe("Prepare Summary", function()
local cases = load_cases("notifications.cases.json")
local prepare_summary = require("prepare_summary")
describe("title handling", function()
it_each(cases.prepare_summary.title_variations, "$desc", function(tc)
local props = { title = tc.title, items = {} }
local result = prepare_summary(props)
expect(result.title).toBe(tc.expected_title)
end)
end)
describe("items processing", function()
it_each(cases.prepare_summary.items_processing, "$desc", function(tc)
local props = { items = tc.items }
local result = prepare_summary(props)
expect(#result.items).toBe(tc.expected_count)
end)
it("enriches items with severity classes", function()
local props = {
items = {
{ label = "Test", count = 5, severity = "warning" }
}
}
local result = prepare_summary(props)
expect(result.items[1].classes).toBe("summary-item--warning")
end)
end)
end)
-- Notification Center functionality tests
-- Uses lua_test framework with parameterized test cases
describe("Calculate Total", function()
local cases = load_cases("notifications.cases.json")
local calculate_total = require("calculate_total")
it_each(cases.calculate_total, "$desc", function(tc)
local result = calculate_total(tc.items)
expect(result).toBe(tc.expected)
end)
end)
describe("Get Severity Class", function()
local cases = load_cases("notifications.cases.json")
local get_severity_class = require("get_severity_class")
it_each(cases.get_severity_class, "$desc", function(tc)
local result = get_severity_class(tc.severity)
expect(result).toBe(tc.expected)
end)
end)
describe("Toast Notifications", function()
local cases = load_cases("notifications.cases.json")
describe("success toast", function()
local toast_success = require("toast_success")
it("creates success toast with correct properties", function()
local result = toast_success("Test message", 5000)
expect(result.type).toBe("toast")
expect(result.variant).toBe("success")
expect(result.message).toBe("Test message")
expect(result.duration).toBe(5000)
expect(result.icon).toBe("check")
end)
it_each(cases.toast_duration, "$desc", function(tc)
local toast_success = require("toast_success")
local result = toast_success(tc.message, tc.duration)
expect(result.duration).toBe(tc.expected_duration)
end)
end)
describe("error toast", function()
local toast_error = require("toast_error")
it("creates error toast with correct properties", function()
local result = toast_error("Error message")
expect(result.type).toBe("toast")
expect(result.variant).toBe("error")
expect(result.message).toBe("Error message")
end)
end)
describe("warning toast", function()
local toast_warning = require("toast_warning")
it("creates warning toast with correct properties", function()
local result = toast_warning("Warning message")
expect(result.type).toBe("toast")
expect(result.variant).toBe("warning")
expect(result.message).toBe("Warning message")
end)
end)
describe("info toast", function()
local toast_info = require("toast_info")
it("creates info toast with correct properties", function()
local result = toast_info("Info message")
expect(result.type).toBe("toast")
expect(result.variant).toBe("info")
expect(result.message).toBe("Info message")
end)
end)
end)
describe("Prepare Summary", function()
local cases = load_cases("notifications.cases.json")
local prepare_summary = require("prepare_summary")
describe("title handling", function()
it_each(cases.prepare_summary.title_variations, "$desc", function(tc)
local props = { title = tc.title, items = {} }
local result = prepare_summary(props)
expect(result.title).toBe(tc.expected_title)
end)
end)
describe("items processing", function()
it_each(cases.prepare_summary.items_processing, "$desc", function(tc)
local props = { items = tc.items }
local result = prepare_summary(props)
expect(#result.items).toBe(tc.expected_count)
end)
it("enriches items with severity classes", function()
local props = {
items = {
{ label = "Test", count = 5, severity = "warning" }
}
}
local result = prepare_summary(props)
expect(result.items[1].classes).toBe("summary-item--warning")
end)
end)
end)