package gobackend import ( "bytes" "encoding/json" "os" "path/filepath" "testing" "time" "github.com/dop251/goja" ) func setStorageValue(t *testing.T, runtime *extensionRuntime, key string, value interface{}) { t.Helper() result := runtime.storageSet(goja.FunctionCall{ Arguments: []goja.Value{ runtime.vm.ToValue(key), runtime.vm.ToValue(value), }, }) if !result.ToBoolean() { t.Fatalf("storage.set(%q) returned false", key) } } func readStorageMap(t *testing.T, storagePath string) map[string]interface{} { t.Helper() data, err := os.ReadFile(storagePath) if err != nil { t.Fatalf("failed to read storage file: %v", err) } var parsed map[string]interface{} if err := json.Unmarshal(data, &parsed); err != nil { t.Fatalf("failed to unmarshal storage file: %v", err) } return parsed } func TestExtensionRuntimeStorage_DebouncedWriteCompactJSON(t *testing.T) { ext := &loadedExtension{ ID: "storage-test", Manifest: &ExtensionManifest{ Name: "storage-test", }, DataDir: t.TempDir(), } runtime := newExtensionRuntime(ext) runtime.storageFlushDelay = 25 * time.Millisecond runtime.RegisterAPIs(goja.New()) setStorageValue(t, runtime, "k1", "v1") setStorageValue(t, runtime, "k2", 2) storagePath := filepath.Join(ext.DataDir, "storage.json") deadline := time.Now().Add(1500 * time.Millisecond) var raw []byte for time.Now().Before(deadline) { data, err := os.ReadFile(storagePath) if err == nil { raw = data break } time.Sleep(20 * time.Millisecond) } if len(raw) == 0 { t.Fatalf("storage.json was not written within timeout") } var parsed map[string]interface{} if err := json.Unmarshal(raw, &parsed); err != nil { t.Fatalf("failed to unmarshal storage file: %v", err) } if parsed["k1"] != "v1" { t.Fatalf("expected k1=v1, got %v", parsed["k1"]) } if parsed["k2"] != float64(2) { t.Fatalf("expected k2=2, got %v", parsed["k2"]) } if bytes.Contains(raw, []byte("\n")) { t.Fatalf("expected compact JSON without indentation, got: %q", string(raw)) } } func TestUnloadExtension_FlushesPendingStorage(t *testing.T) { ext := &loadedExtension{ ID: "unload-storage-test", Manifest: &ExtensionManifest{ Name: "unload-storage-test", }, DataDir: t.TempDir(), VM: goja.New(), } runtime := newExtensionRuntime(ext) runtime.storageFlushDelay = time.Hour runtime.RegisterAPIs(ext.VM) ext.runtime = runtime manager := &extensionManager{ extensions: map[string]*loadedExtension{ ext.ID: ext, }, } setStorageValue(t, runtime, "persist_on_unload", true) if err := manager.UnloadExtension(ext.ID); err != nil { t.Fatalf("UnloadExtension failed: %v", err) } storagePath := filepath.Join(ext.DataDir, "storage.json") parsed := readStorageMap(t, storagePath) if parsed["persist_on_unload"] != true { t.Fatalf("expected pending storage value to be flushed on unload, got %v", parsed["persist_on_unload"]) } }