Skip to content
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
5a67386
feat: replace ticker-based balancer polling with event-driven idle wo…
SuperCoolPencil Jun 29, 2026
e81c9fc
refactor: Create internal/types and merge engine/types and engine/events
SuperCoolPencil Jun 29, 2026
2dd8c04
refactor: Create internal/store from engine/state
SuperCoolPencil Jun 29, 2026
1dbb6fc
refactor: Create internal/progress from types
SuperCoolPencil Jun 29, 2026
6e57337
refactor: Create internal/transport from engine root files
SuperCoolPencil Jun 29, 2026
cc3e58f
refactor: Create internal/strategy/concurrent and single
SuperCoolPencil Jun 29, 2026
5fc52e3
refactor: Create internal/scheduler and delete download
SuperCoolPencil Jun 29, 2026
d59dc86
refactor: Create internal/service from core
SuperCoolPencil Jun 29, 2026
475f849
refactor: remove extensive legacy unit and integration tests and intr…
SuperCoolPencil Jun 29, 2026
8c7bc0f
test: add comprehensive unit tests for EventBus, LifecycleManager, an…
SuperCoolPencil Jun 29, 2026
a6a0ea6
test: rewrite unit tests for local and remote service layer (Phase 2)
SuperCoolPencil Jun 29, 2026
e35dc47
test: implement comprehensive unit test suite and add rate limit pars…
SuperCoolPencil Jun 30, 2026
edbee2e
add comprehensive test suite across internal packages and implement s…
SuperCoolPencil Jul 1, 2026
8d22e06
refactor: remove obsolete test suite and cleanup legacy test files
SuperCoolPencil Jul 1, 2026
132b6b4
chore: remove temporary migration script rename_state_to_store.sh
SuperCoolPencil Jul 1, 2026
2dbe0c4
Phase 2: Complete migration to strictly typed DownloadEvent
SuperCoolPencil Jul 1, 2026
39e9dfe
Phase 2: Finalize test mocks for typed events
SuperCoolPencil Jul 1, 2026
d963b9c
refactor: rename DownloadConfig to DownloadRecord and consolidate sta…
SuperCoolPencil Jul 1, 2026
1c19a11
refactor: decouple progress state by introducing internal structures …
SuperCoolPencil Jul 1, 2026
d5ffbe4
refactor: improve event bus thread safety and convert progress tracki…
SuperCoolPencil Jul 1, 2026
5fc4d36
test: update downloader test to use atomic load for TotalSize verific…
SuperCoolPencil Jul 1, 2026
0a99538
refactor: move CfgProgress helper to progress package and clean up fo…
SuperCoolPencil Jul 1, 2026
c732ff7
refactor: implement event bus shutdown drainage and enhance resume va…
SuperCoolPencil Jul 1, 2026
f38f5a4
feat: allow manual override of TotalSize and SupportsRange in downloa…
SuperCoolPencil Jul 1, 2026
7b213bf
fix: use configuration filename instead of saved state during downloa…
SuperCoolPencil Jul 1, 2026
79d607a
refactor: remove redundant totalSize and supportsRange parameters fro…
SuperCoolPencil Jul 1, 2026
f43b051
fix: return empty master list instead of error on version mismatch
SuperCoolPencil Jul 1, 2026
19906df
refactor: ensure GracefulShutdown execution is idempotent using sync.…
SuperCoolPencil Jul 1, 2026
9699f85
refactor: simplify boolean assignment for progress event types in eve…
SuperCoolPencil Jul 1, 2026
16fd71c
refactor: improve test reliability by migrating to t.Cleanup and t.Se…
SuperCoolPencil Jul 1, 2026
483a673
refactor: remove redundant block scope in countingLifecycleService Pu…
SuperCoolPencil Jul 1, 2026
25f1199
refactor: use errors.Is for error comparisons, cleanup whitespace, an…
SuperCoolPencil Jul 1, 2026
25c244e
feat: include task state in DownloadRecord and return empty string on…
SuperCoolPencil Jul 2, 2026
6bdd5e2
refactor: migrate download cleanup commands to use remote API instead…
SuperCoolPencil Jul 2, 2026
acf9225
fix: allow ResumeBatch to recover by ignoring corrupt download state …
SuperCoolPencil Jul 2, 2026
a9b00c2
refactor: update progress mapping to use DownloadRecord instead of Do…
SuperCoolPencil Jul 2, 2026
306a4cc
test: include SystemRoot environment variable and increase Retry-Afte…
SuperCoolPencil Jul 2, 2026
634a846
chore: update package-lock.json dependencies
SuperCoolPencil Jul 2, 2026
b33d3f2
chore: add pretest script to run wxt prepare before vitest
SuperCoolPencil Jul 2, 2026
cf91657
test: update integration test to match camelCase to snake_case field …
SuperCoolPencil Jul 2, 2026
eb495ce
Merge branch 'main' into eliminate-polling
SuperCoolPencil Jul 6, 2026
9ae9ffa
refactor: fix concurrency safety in store, simplify progress type ass…
SuperCoolPencil Jul 6, 2026
cb326de
feat: add startup warning for config load errors and enforce strict m…
SuperCoolPencil Jul 6, 2026
e869fbd
fix: update cleanupOrphans base directory reference to use the provid…
SuperCoolPencil Jul 6, 2026
558e418
fix: handle EventRemoved state cleanup and optimize directory access …
SuperCoolPencil Jul 6, 2026
38ab7b5
test: skip incompatible permission tests on Windows and improve path …
SuperCoolPencil Jul 6, 2026
e5b66ed
refactor: decouple download record creation from pool insertion to pr…
SuperCoolPencil Jul 6, 2026
35be707
fix: allow absolute paths on Windows daemons and skip platform-specif…
SuperCoolPencil Jul 7, 2026
327ddbc
fix: prevent overwriting advanced download statuses during EventQueue…
SuperCoolPencil Jul 7, 2026
35ca820
fix: resolve failing CI tests on windows-latest and lint errors
SuperCoolPencil Jul 7, 2026
bcd69f0
fix: replace httptest.NewServer with testutil.NewHTTPServerT in headl…
SuperCoolPencil Jul 7, 2026
5a8faa7
fix: replace all remaining httptest.NewServer calls with testutil.New…
SuperCoolPencil Jul 7, 2026
76e28e6
test: skip kardianos lifecycle tests on windows-latest socket error
SuperCoolPencil Jul 7, 2026
83ac05e
fix(scheduler): prevent safeSendProgress from blocking indefinitely o…
SuperCoolPencil Jul 7, 2026
473c851
refactor: thread-safe path resolution for detail files and improve ch…
SuperCoolPencil Jul 7, 2026
c86f222
refactor: ensure safe event bus shutdown by adding publication tracki…
SuperCoolPencil Jul 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 17 additions & 32 deletions cmd/autoresume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
"time"

"github.com/SurgeDM/Surge/internal/config"
"github.com/SurgeDM/Surge/internal/core"
"github.com/SurgeDM/Surge/internal/download"
"github.com/SurgeDM/Surge/internal/engine/state"
"github.com/SurgeDM/Surge/internal/engine/types"
"github.com/SurgeDM/Surge/internal/processing"
"github.com/SurgeDM/Surge/internal/orchestrator"
"github.com/SurgeDM/Surge/internal/scheduler"
"github.com/SurgeDM/Surge/internal/service"
"github.com/SurgeDM/Surge/internal/store"
"github.com/SurgeDM/Surge/internal/types"
)

func TestCmd_AutoResume_Execution(t *testing.T) {
Expand Down Expand Up @@ -46,19 +46,19 @@ func TestCmd_AutoResume_Execution(t *testing.T) {
}

// 3. Configure State DB
state.CloseDB() // Ensure clean state
store.CloseDB() // Ensure clean state
dbPath := filepath.Join(surgeDir, "state", "surge.db")
if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil {
t.Fatal(err)
}
state.Configure(dbPath)
store.Configure(dbPath)

// 4. Seed DB with a paused download
testID := "cmd-resume-id-1"
testURL := "http://example.com/cmd-resume.zip"
testDest := filepath.Join(tmpDir, "cmd-resume.zip")

manualState := &types.DownloadState{
manualState := &types.DownloadRecord{
ID: testID,
URL: testURL,
Filename: "cmd-resume.zip",
Expand All @@ -68,7 +68,7 @@ func TestCmd_AutoResume_Execution(t *testing.T) {
PausedAt: time.Now().Unix(),
CreatedAt: time.Now().Unix(),
}
if err := state.AddToMasterList(types.DownloadEntry{
if err := store.AddToMasterList(types.DownloadRecord{
ID: testID,
URL: testURL,
DestPath: testDest,
Expand All @@ -79,34 +79,19 @@ func TestCmd_AutoResume_Execution(t *testing.T) {
}); err != nil {
t.Fatal(err)
}
if err := state.SaveState(testURL, testDest, manualState); err != nil {
if err := store.SaveState(testURL, testDest, manualState); err != nil {
t.Fatal(err)
}

// 5. Initialize GlobalPool + GlobalService
GlobalProgressCh = make(chan any, 10)
GlobalPool = download.NewWorkerPool(GlobalProgressCh, 4)
GlobalService = core.NewLocalDownloadServiceWithInput(GlobalPool, GlobalProgressCh)
GlobalProgressCh = make(chan types.DownloadEvent, 10)
GlobalPool = scheduler.New(GlobalProgressCh, 4)

eventBus := orchestrator.NewEventBus()
getAll := func() []types.DownloadRecord { return GlobalPool.GetAll() }
GlobalLifecycle = orchestrator.NewLifecycleManager(GlobalPool, eventBus, buildActiveDownloadChecker(getAll))
GlobalService = service.NewLocalDownloadService(GlobalLifecycle)

GlobalLifecycle = processing.NewLifecycleManager(nil, nil, nil)
GlobalLifecycle.SetEngineHooks(processing.EngineHooks{
Pause: GlobalPool.Pause,
ExtractPausedConfig: GlobalPool.ExtractPausedConfig,
AddConfig: GlobalPool.Add,
GetStatus: GlobalPool.GetStatus,
Cancel: GlobalPool.Cancel,
UpdateURL: GlobalPool.UpdateURL,
PublishEvent: GlobalService.Publish,
})
if svc, ok := GlobalService.(*core.LocalDownloadService); ok {
svc.SetLifecycleHooks(core.LifecycleHooks{
Pause: GlobalLifecycle.Pause,
Resume: GlobalLifecycle.Resume,
ResumeBatch: GlobalLifecycle.ResumeBatch,
Cancel: GlobalLifecycle.Cancel,
UpdateURL: GlobalLifecycle.UpdateURL,
})
}
defer func() {
_ = GlobalService.Shutdown()
GlobalService = nil
Expand Down
58 changes: 31 additions & 27 deletions cmd/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import (
"testing"

"github.com/SurgeDM/Surge/internal/config"
"github.com/SurgeDM/Surge/internal/core"
"github.com/SurgeDM/Surge/internal/download"
"github.com/SurgeDM/Surge/internal/engine/state"
"github.com/SurgeDM/Surge/internal/engine/types"
"github.com/SurgeDM/Surge/internal/orchestrator"
"github.com/SurgeDM/Surge/internal/scheduler"
"github.com/SurgeDM/Surge/internal/service"
"github.com/SurgeDM/Surge/internal/store"
"github.com/SurgeDM/Surge/internal/testutil"
"github.com/SurgeDM/Surge/internal/types"
"github.com/SurgeDM/Surge/internal/utils"
)

Expand Down Expand Up @@ -52,7 +53,7 @@ func TestResolveDownloadID_Remote(t *testing.T) {
if err := config.EnsureDirs(); err != nil {
t.Fatalf("EnsureDirs failed: %v", err)
}
state.Configure(filepath.Join(tempDir, "surge.db"))
store.Configure(filepath.Join(tempDir, "surge.db"))
saveActivePort(port)
defer removeActivePort()

Expand Down Expand Up @@ -92,8 +93,8 @@ func TestResolveDownloadID_RemoteStillWorksWhenDBUnavailable(t *testing.T) {
globalToken = origToken
})

state.CloseDB()
state.Configure(filepath.Join(t.TempDir(), "missing", "surge.db")) // Intentionally invalid path
store.CloseDB()
store.Configure(filepath.Join(t.TempDir(), "missing", "surge.db")) // Intentionally invalid path

full, err := resolveDownloadID("ddeeff")
if err != nil {
Expand All @@ -107,11 +108,11 @@ func TestResolveDownloadID_RemoteStillWorksWhenDBUnavailable(t *testing.T) {
func TestResolveDownloadID_StrictRemoteDoesNotFallbackToDBOnRemoteError(t *testing.T) {
setupIsolatedCmdState(t)

entry := types.DownloadEntry{
entry := types.DownloadRecord{
ID: "11223344-1234-5678-90ab-cdef12345678",
Filename: "db-only.bin",
}
if err := state.AddToMasterList(entry); err != nil {
if err := store.AddToMasterList(entry); err != nil {
t.Fatalf("failed to seed db entry: %v", err)
}

Expand Down Expand Up @@ -146,11 +147,11 @@ func TestResolveDownloadID_StrictRemoteDoesNotFallbackToDBOnRemoteError(t *testi
func TestResolveDownloadID_LocalModeFallsBackToDBWhenRemoteListFails(t *testing.T) {
setupIsolatedCmdState(t)

entry := types.DownloadEntry{
entry := types.DownloadRecord{
ID: "99aabbcc-1234-5678-90ab-cdef12345678",
Filename: "fallback.bin",
}
if err := state.AddToMasterList(entry); err != nil {
if err := store.AddToMasterList(entry); err != nil {
t.Fatalf("failed to seed db entry: %v", err)
}

Expand Down Expand Up @@ -440,7 +441,7 @@ func TestRmClean_Offline_Works(t *testing.T) {
setupIsolatedCmdState(t)
removeActivePort() // Ensure offline mode

completed := types.DownloadEntry{
completed := types.DownloadRecord{
ID: "rm-clean-offline-id",
URL: "https://example.com/completed.bin",
Filename: "completed.bin",
Expand All @@ -450,7 +451,7 @@ func TestRmClean_Offline_Works(t *testing.T) {
Downloaded: 100,
CompletedAt: 1,
}
if err := state.AddToMasterList(completed); err != nil {
if err := store.AddToMasterList(completed); err != nil {
t.Fatalf("failed to seed completed download: %v", err)
}

Expand All @@ -465,7 +466,7 @@ func TestRmClean_Offline_Works(t *testing.T) {
}
})

entry, err := state.GetDownload(completed.ID)
entry, err := store.GetDownload(completed.ID)
if err != nil {
t.Fatalf("failed to query completed entry after clean: %v", err)
}
Expand Down Expand Up @@ -695,12 +696,12 @@ func TestActionCommandsRunE_ReturnAmbiguousIDErrors(t *testing.T) {
saveActivePort(port)
t.Cleanup(removeActivePort)

entries := []types.DownloadEntry{
entries := []types.DownloadRecord{
{ID: "deadbeef-1234-5678-90ab-cdef12345678", Filename: "first.bin"},
{ID: "deadbead-1234-5678-90ab-cdef12345678", Filename: "second.bin"},
}
for _, entry := range entries {
if err := state.AddToMasterList(entry); err != nil {
if err := store.AddToMasterList(entry); err != nil {
t.Fatalf("failed to seed db entry %s: %v", entry.ID, err)
}
}
Expand All @@ -720,15 +721,15 @@ func TestPrintDownloads_FromDatabase_TableAndJSON(t *testing.T) {
setupIsolatedCmdState(t)
removeActivePort()

entry := types.DownloadEntry{
entry := types.DownloadRecord{
ID: "12345678-1234-1234-1234-1234567890ab",
URL: "https://example.com/asset.bin",
Filename: "this-is-a-very-long-file-name-that-should-truncate.bin",
Status: "downloading",
Downloaded: 512,
TotalSize: 1024,
}
if err := state.AddToMasterList(entry); err != nil {
if err := store.AddToMasterList(entry); err != nil {
t.Fatalf("failed to seed db entry: %v", err)
}

Expand Down Expand Up @@ -789,12 +790,12 @@ func TestPrintDownloads_JSONEmpty(t *testing.T) {
func TestPrintDownloads_StrictRemoteEmpty_DoesNotFallbackToDB(t *testing.T) {
setupIsolatedCmdState(t)

entry := types.DownloadEntry{
entry := types.DownloadRecord{
ID: "feedface-1234-5678-90ab-cdef12345678",
Filename: "local-only.bin",
Status: "completed",
}
if err := state.AddToMasterList(entry); err != nil {
if err := store.AddToMasterList(entry); err != nil {
t.Fatalf("failed to seed local db entry: %v", err)
}

Expand Down Expand Up @@ -822,15 +823,15 @@ func TestShowDownloadDetails_UsesDatabaseFallback(t *testing.T) {
setupIsolatedCmdState(t)
removeActivePort()

entry := types.DownloadEntry{
entry := types.DownloadRecord{
ID: "87654321-1234-1234-1234-1234567890ab",
URL: "https://example.com/detail.bin",
Filename: "detail.bin",
Status: "completed",
Downloaded: 250,
TotalSize: 500,
}
if err := state.AddToMasterList(entry); err != nil {
if err := store.AddToMasterList(entry); err != nil {
t.Fatalf("failed to seed db entry: %v", err)
}

Expand Down Expand Up @@ -1038,9 +1039,12 @@ func TestProcessDownloads_RemoteAndLocal(t *testing.T) {
setupIsolatedCmdState(t)
atomic.StoreInt32(&activeDownloads, 0)

GlobalProgressCh = make(chan any, 10)
GlobalPool = download.NewWorkerPool(GlobalProgressCh, 2)
GlobalService = core.NewLocalDownloadService(GlobalPool)
GlobalProgressCh = make(chan types.DownloadEvent, 10)
GlobalPool = scheduler.New(GlobalProgressCh, 2)
eventBus := orchestrator.NewEventBus()
getAll := func() []types.DownloadRecord { return GlobalPool.GetAll() }
GlobalLifecycle = orchestrator.NewLifecycleManager(GlobalPool, eventBus, buildActiveDownloadChecker(getAll))
GlobalService = service.NewLocalDownloadService(GlobalLifecycle)

probeServer := testutil.NewHTTPServerT(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "5")
Expand Down Expand Up @@ -1079,8 +1083,8 @@ func setupIsolatedCmdState(t *testing.T) {
t.Fatalf("EnsureDirs failed: %v", err)
}

state.CloseDB()
state.Configure(filepath.Join(config.GetStateDir(), "surge.db"))
store.CloseDB()
store.Configure(filepath.Join(config.GetStateDir(), "surge.db"))
}

func resetCommandConnectionState(t *testing.T) {
Expand Down
Loading
Loading