Files
MyGoNavi/internal/app/import_task_runtime_test.go
Syngnat 390a249105 feat(data-import): 重构大文件导入与安全任务运行时
- SQL、CSV、JSON 与 XLSX 改为资源受限的流式解析,并支持压缩、编码和方言预检

- 增加遇错停止/继续策略、持久任务、错误行导出、源文件身份与安全取消

- 为数据库驱动补齐上下文事务和写入结果未知语义,避免失败重放与脏会话复用
2026-08-08 20:51:23 +08:00

155 lines
4.3 KiB
Go

package app
import (
"context"
"sync/atomic"
"testing"
"time"
"GoNavi-Wails/internal/importjob"
)
func TestCancelAndWaitImportTasksKeepsRegistrationUntilCleanup(t *testing.T) {
app := NewApp()
ctx, cancel := context.WithCancel(context.Background())
cleanup, registered := app.registerImportTask("shutdown-import", cancel)
if !registered {
t.Fatal("import task registration failed")
}
done := make(chan struct{})
go func() {
<-ctx.Done()
cleanup()
close(done)
}()
if !app.cancelAndWaitImportTasks(time.Second) {
t.Fatal("import task did not unwind before shutdown deadline")
}
select {
case <-done:
default:
t.Fatal("shutdown returned before task cleanup")
}
app.queryMu.RLock()
_, retained := app.runningQueries["shutdown-import"]
app.queryMu.RUnlock()
if retained {
t.Fatal("completed import registration leaked")
}
}
func TestCancelAndWaitImportTasksRejectsRegistrationsAfterShutdownStarts(t *testing.T) {
app := NewApp()
if !app.cancelAndWaitImportTasks(0) {
t.Fatal("empty import runtime did not finish shutdown")
}
cleanup, registered := app.registerImportTask("late-shutdown-import", func() {})
defer cleanup()
if registered {
t.Fatal("import task registered after import runtime shutdown started")
}
}
func TestCancelImportJobBeforeLifecycleBindingPersistsStoppingWhenBound(t *testing.T) {
app := NewApp()
app.configDir = t.TempDir()
var cancelCalls atomic.Int32
cleanup, registered := app.registerImportTask("late-bound-import", func() {
cancelCalls.Add(1)
}, importjob.KindTable)
if !registered {
t.Fatal("import task registration failed")
}
defer cleanup()
first := app.CancelImportJob("late-bound-import")
second := app.CancelImportJob("late-bound-import")
if !first.Success || !second.Success {
t.Fatalf("repeated cancellation should be idempotent: first=%#v second=%#v", first, second)
}
if got := cancelCalls.Load(); got != 1 {
t.Fatalf("cancel callback calls = %d, want 1", got)
}
lifecycle, err := app.beginManagedImportJob(managedImportJobStart{
ID: "late-bound-import",
Kind: importjob.KindTable,
})
if err != nil {
t.Fatal(err)
}
stored, err := lifecycle.store.Get("late-bound-import")
if err != nil {
t.Fatal(err)
}
if stored.Status != importjob.StatusStopping {
t.Fatalf("status = %q, want %q", stored.Status, importjob.StatusStopping)
}
if result := app.CancelImportJob("late-bound-import"); !result.Success {
t.Fatalf("cancelling an already-stopping import should succeed: %#v", result)
}
afterRepeatedCancel, err := lifecycle.store.Get("late-bound-import")
if err != nil {
t.Fatal(err)
}
if afterRepeatedCancel.Revision != stored.Revision {
t.Fatalf("repeated cancellation rewrote stopping job: before=%d after=%d", stored.Revision, afterRepeatedCancel.Revision)
}
if got := cancelCalls.Load(); got != 1 {
t.Fatalf("cancel callback calls after lifecycle bind = %d, want 1", got)
}
}
func TestCancelImportJobDoesNotCancelOrdinaryRunningQuery(t *testing.T) {
app := NewApp()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cleanup, registered := app.registerExclusiveRunningQuery("ordinary-query", cancel, true)
if !registered {
t.Fatal("query registration failed")
}
defer cleanup()
result := app.CancelImportJob("ordinary-query")
if result.Success {
t.Fatalf("ordinary query cancellation unexpectedly succeeded: %#v", result)
}
select {
case <-ctx.Done():
t.Fatal("import cancellation touched an ordinary running query")
default:
}
}
func TestCancelImportTaskByKindRejectsDifferentImportKind(t *testing.T) {
app := NewApp()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cleanup, registered := app.registerImportTask("table-import-kind", cancel, importjob.KindTable)
if !registered {
t.Fatal("import task registration failed")
}
defer cleanup()
wrongKind := app.cancelImportTaskByKind("table-import-kind", importjob.KindSQL)
if wrongKind.Success {
t.Fatalf("wrong-kind cancellation unexpectedly succeeded: %#v", wrongKind)
}
select {
case <-ctx.Done():
t.Fatal("wrong-kind cancellation reached the task")
default:
}
matchingKind := app.cancelImportTaskByKind("table-import-kind", importjob.KindTable)
if !matchingKind.Success {
t.Fatalf("matching-kind cancellation failed: %#v", matchingKind)
}
select {
case <-ctx.Done():
default:
t.Fatal("matching-kind cancellation did not reach the task")
}
}