docs: rewrite PLAN.md and update CODEBASE.md, README.md
- Full rewrite of PLAN.md to reflect current architecture (27 REST + 3 WS endpoints, embedded frontend, automation, scheduler, health check, GoReleaser CI) - Added health.go, mods.go, scheduler.go, robfig/cron dep to CODEBASE.md - Added Gitea Actions CI/CD section to CODEBASE.md - Added conventional commits to code style section - Added missing API routes and embed/ to README.md
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"arma3-web-server/internal/models"
|
||||
)
|
||||
|
||||
func TestParseModlistHTML_SingleMod(t *testing.T) {
|
||||
html := `<html><body><table>
|
||||
<tr data-type="ModContainer">
|
||||
<td data-type="DisplayName">CBA_A3</td>
|
||||
<td><span class="from-steam">Steam</span></td>
|
||||
<td><a href="https://steamcommunity.com/sharedfiles/filedetails/?id=450814997">link</a></td>
|
||||
</tr>
|
||||
</table></body></html>`
|
||||
|
||||
_, mods, err := ParseModlistHTML(strings.NewReader(html))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseModlistHTML() error = %v", err)
|
||||
}
|
||||
if len(mods) != 1 {
|
||||
t.Fatalf("got %d mods, want 1", len(mods))
|
||||
}
|
||||
if mods[0].ID != "450814997" {
|
||||
t.Errorf("mods[0].ID = %q, want %q", mods[0].ID, "450814997")
|
||||
}
|
||||
if mods[0].Name != "CBA_A3" {
|
||||
t.Errorf("mods[0].Name = %q, want %q", mods[0].Name, "CBA_A3")
|
||||
}
|
||||
if !mods[0].Enabled {
|
||||
t.Error("mods[0].Enabled should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModlistHTML_MultipleMods(t *testing.T) {
|
||||
html := `<html><body><table>
|
||||
<tr data-type="ModContainer">
|
||||
<td data-type="DisplayName">CBA_A3</td>
|
||||
<td><span>Steam</span></td>
|
||||
<td><a href="https://steamcommunity.com/sharedfiles/filedetails/?id=450814997">link</a></td>
|
||||
</tr>
|
||||
<tr data-type="ModContainer">
|
||||
<td data-type="DisplayName">ACE3</td>
|
||||
<td><span>Steam</span></td>
|
||||
<td><a href="https://steamcommunity.com/sharedfiles/filedetails/?id=463939057">link</a></td>
|
||||
</tr>
|
||||
</table></body></html>`
|
||||
|
||||
_, mods, err := ParseModlistHTML(strings.NewReader(html))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseModlistHTML() error = %v", err)
|
||||
}
|
||||
if len(mods) != 2 {
|
||||
t.Fatalf("got %d mods, want 2", len(mods))
|
||||
}
|
||||
if mods[0].Name != "CBA_A3" || mods[1].Name != "ACE3" {
|
||||
t.Errorf("mods = [%q, %q], want [CBA_A3, ACE3]", mods[0].Name, mods[1].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModlistHTML_Deduplication(t *testing.T) {
|
||||
html := `<html><body><table>
|
||||
<tr data-type="ModContainer">
|
||||
<td data-type="DisplayName">CBA_A3</td>
|
||||
<td><span>Steam</span></td>
|
||||
<td><a href="https://steamcommunity.com/sharedfiles/filedetails/?id=450814997">link</a></td>
|
||||
</tr>
|
||||
<tr data-type="ModContainer">
|
||||
<td data-type="DisplayName">CBA_A3</td>
|
||||
<td><span>Steam</span></td>
|
||||
<td><a href="https://steamcommunity.com/sharedfiles/filedetails/?id=450814997">link</a></td>
|
||||
</tr>
|
||||
</table></body></html>`
|
||||
|
||||
_, mods, _ := ParseModlistHTML(strings.NewReader(html))
|
||||
if len(mods) != 1 {
|
||||
t.Errorf("got %d mods, want 1 (deduplication)", len(mods))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModlistHTML_EmptyTable(t *testing.T) {
|
||||
html := `<html><body><table></table></body></html>`
|
||||
_, mods, err := ParseModlistHTML(strings.NewReader(html))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseModlistHTML() error = %v", err)
|
||||
}
|
||||
if len(mods) != 0 {
|
||||
t.Errorf("got %d mods, want 0", len(mods))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModlistHTML_InvalidHTML(t *testing.T) {
|
||||
// Go's html parser is lenient and returns empty results for garbage input
|
||||
_, mods, err := ParseModlistHTML(strings.NewReader("not html at all <>"))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseModlistHTML() should not error on lenient parser, got: %v", err)
|
||||
}
|
||||
if len(mods) != 0 {
|
||||
t.Errorf("ParseModlistHTML() returned %d mods for garbage input, want 0", len(mods))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderModlistHTML(t *testing.T) {
|
||||
mods := []models.ModEntry{
|
||||
{ID: "450814997", Name: "CBA_A3", Enabled: true},
|
||||
{ID: "463939057", Name: "ACE3", Enabled: true},
|
||||
}
|
||||
|
||||
output, err := RenderModlistHTML("Test", mods)
|
||||
if err != nil {
|
||||
t.Fatalf("RenderModlistHTML() error = %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "450814997") {
|
||||
t.Error("output should contain mod ID 450814997")
|
||||
}
|
||||
if !strings.Contains(output, "CBA_A3") {
|
||||
t.Error("output should contain mod name CBA_A3")
|
||||
}
|
||||
if !strings.Contains(output, "463939057") {
|
||||
t.Error("output should contain mod ID 463939057")
|
||||
}
|
||||
if !strings.Contains(output, "<?xml") {
|
||||
t.Error("output should start with XML declaration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderModlistHTML_SkipsEmptyIDs(t *testing.T) {
|
||||
mods := []models.ModEntry{
|
||||
{ID: "", Name: "No ID", Enabled: true},
|
||||
{ID: "450814997", Name: "CBA_A3", Enabled: true},
|
||||
}
|
||||
|
||||
output, err := RenderModlistHTML("Test", mods)
|
||||
if err != nil {
|
||||
t.Fatalf("RenderModlistHTML() error = %v", err)
|
||||
}
|
||||
if strings.Contains(output, "No ID") {
|
||||
t.Error("output should not contain mod with empty ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileNameToModlistName(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{"my_modlist.html", "my modlist"},
|
||||
{"LibUkraine Final (3).html", "LibUkraine Final (3)"},
|
||||
{"test-file-name.html", "test file name"},
|
||||
{"simple.html", "simple"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := FileNameToModlistName(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("FileNameToModlistName(%q) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanModName(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{"CBA_A3", "CBA_A3"},
|
||||
{"mod/name", "mod_name"},
|
||||
{"mod\\name", "mod_name"},
|
||||
{" spaces ", "spaces"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := cleanModName(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("cleanModName(%q) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user