503 lines
17 KiB
Go
503 lines
17 KiB
Go
package releases
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"ymhut-box/server/unified-management/internal/config"
|
|
"ymhut-box/server/unified-management/internal/db"
|
|
)
|
|
|
|
func TestRequestBaseURLUsesTrustedForwardedValues(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://internal:33550/downloads/file.zip", nil)
|
|
req.Header.Set("X-Forwarded-Proto", "HTTPS, http")
|
|
req.Header.Set("X-Forwarded-Host", "updates.example.com:8443, internal:33550")
|
|
|
|
if got := requestBaseURL(req, "https://fallback.example.com/"); got != "https://updates.example.com:8443" {
|
|
t.Fatalf("requestBaseURL() = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestRequestBaseURLRejectsInvalidForwardedValues(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
proto string
|
|
host string
|
|
request string
|
|
fallback string
|
|
want string
|
|
}{
|
|
{name: "invalid scheme", proto: "javascript", host: "updates.example.com", request: "https://internal:33550/path", want: "https://updates.example.com"},
|
|
{name: "invalid forwarded host", proto: "https", host: "user@evil.example", request: "http://internal:33550/path", want: "https://internal:33550"},
|
|
{name: "invalid forwarded port", proto: "https", host: "updates.example.com:99999", request: "http://internal:33550/path", want: "https://internal:33550"},
|
|
{name: "invalid request host", proto: "https", host: "evil.example/path", request: "http://internal/path", fallback: "https://fallback.example.com/", want: "https://fallback.example.com"},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, test.request, nil)
|
|
req.Header.Set("X-Forwarded-Proto", test.proto)
|
|
req.Header.Set("X-Forwarded-Host", test.host)
|
|
if test.name == "invalid request host" {
|
|
req.Host = "bad host"
|
|
}
|
|
if got := requestBaseURL(req, test.fallback); got != test.want {
|
|
t.Fatalf("requestBaseURL() = %q, want %q", got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompareVersion(t *testing.T) {
|
|
cases := []struct {
|
|
a string
|
|
b string
|
|
want int
|
|
}{
|
|
{"2.0.6.31", "2.0.6.2", 1},
|
|
{"2.0.10", "2.0.9", 1},
|
|
{"2.0.6.2", "2.0.6.31", -1},
|
|
{"2.0.6", "2.0.6.0", 0},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := compareVersion(tc.a, tc.b); got != tc.want {
|
|
t.Fatalf("compareVersion(%q, %q) = %d, want %d", tc.a, tc.b, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDetectPackageMetadata(t *testing.T) {
|
|
platform, arch := detectPlatform("YMhutBox_2.0.6.31_x64.msix")
|
|
if platform != "windows" || arch != "x64" {
|
|
t.Fatalf("detectPlatform returned %s/%s", platform, arch)
|
|
}
|
|
if version := detectVersion("YMhut_Box_WinUI_Setup_2.0.6.31.exe"); version != "2.0.6.31" {
|
|
t.Fatalf("detectVersion returned %q", version)
|
|
}
|
|
}
|
|
|
|
func TestScanPackagesUsesCDNAndCachesHashes(t *testing.T) {
|
|
dir := t.TempDir()
|
|
name := "YMhut_Box_WinUI_Setup_2.0.7_x64.exe"
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte("stable package"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service := NewService(&config.Config{
|
|
DownloadsDir: dir,
|
|
BaseURL: "https://update.ymhut.cn",
|
|
CDNBaseURL: "https://cdn.ymhut.cn/",
|
|
}, nil)
|
|
req := httptest.NewRequest("GET", "https://origin.example/api/client/releases", nil)
|
|
|
|
first := service.ScanPackages(req)
|
|
second := service.ScanPackages(req)
|
|
|
|
if len(first) != 1 || first[0].URL != "https://cdn.ymhut.cn/downloads/"+name {
|
|
t.Fatalf("unexpected CDN package: %#v", first)
|
|
}
|
|
if len(second) != 1 || first[0].SHA256 == "" || second[0].SHA256 != first[0].SHA256 {
|
|
t.Fatalf("cached hashes differ: first=%#v second=%#v", first, second)
|
|
}
|
|
if len(service.hashes) != 1 {
|
|
t.Fatalf("expected one cached hash, got %d", len(service.hashes))
|
|
}
|
|
}
|
|
|
|
func TestReconcileListsEverySupportedPackageType(t *testing.T) {
|
|
dir := t.TempDir()
|
|
for _, name := range []string{"app.exe", "app.msix", "app.appinstaller", "app.msi", "app.zip", "app.7z"} {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(name), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "notes.txt"), []byte("ignored"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service := NewService(&config.Config{DownloadsDir: dir, BaseURL: "https://update.example"}, nil)
|
|
if got := len(service.ScanPackages(httptest.NewRequest("GET", "https://update.example/api/client/releases", nil))); got != 6 {
|
|
t.Fatalf("listed %d packages, want 6", got)
|
|
}
|
|
}
|
|
|
|
func TestReconcileReusesPersistentHashAfterRestart(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := &config.Config{
|
|
StorageDir: filepath.Join(dir, "storage"),
|
|
DownloadsDir: filepath.Join(dir, "downloads"),
|
|
BaseURL: "https://update.example",
|
|
Database: config.DatabaseConfig{
|
|
Provider: "sqlite",
|
|
SQLitePath: filepath.Join(dir, "storage", "unified.sqlite"),
|
|
HealthIntervalSec: 30,
|
|
},
|
|
}
|
|
if err := os.MkdirAll(cfg.DownloadsDir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
name := "YMhut_Box_2.1.0_x64.zip"
|
|
if err := os.WriteFile(filepath.Join(cfg.DownloadsDir, name), []byte("persistent package hash"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store, err := db.Open(cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
first := NewService(cfg, store)
|
|
if packages := first.ScanPackages(httptest.NewRequest("GET", "https://update.example", nil)); len(packages) != 1 || packages[0].SHA256 == "" {
|
|
t.Fatalf("initial reconcile did not index the package: %#v", packages)
|
|
}
|
|
hashCalls := 0
|
|
second := newService(cfg, store, func(path string) string {
|
|
hashCalls++
|
|
return sha256File(path)
|
|
})
|
|
if packages := second.ScanPackages(httptest.NewRequest("GET", "https://update.example", nil)); len(packages) != 1 {
|
|
t.Fatalf("restart snapshot contains %d packages, want 1", len(packages))
|
|
}
|
|
if hashCalls != 0 {
|
|
t.Fatalf("restart rehashed %d files, want 0", hashCalls)
|
|
}
|
|
}
|
|
|
|
func TestReconcileRehashesChangedFileAndRemovesDeletedIndex(t *testing.T) {
|
|
service, cfg, cleanup := newPreparedPackageTestService(t)
|
|
defer cleanup()
|
|
name := "YMhut_Box_2.2.0_x64.zip"
|
|
path := filepath.Join(cfg.DownloadsDir, name)
|
|
if err := os.MkdirAll(cfg.DownloadsDir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte("first"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hashCalls := 0
|
|
service.hashFile = func(path string) string {
|
|
hashCalls++
|
|
return sha256File(path)
|
|
}
|
|
if err := service.Reconcile(t.Context()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first := service.ScanPackages(nil)
|
|
if len(first) != 1 || hashCalls != 1 {
|
|
t.Fatalf("first reconcile packages=%d hashCalls=%d", len(first), hashCalls)
|
|
}
|
|
if err := os.WriteFile(path, []byte("changed package bytes"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := service.Reconcile(t.Context()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second := service.ScanPackages(nil)
|
|
if hashCalls != 2 || len(second) != 1 || second[0].SHA256 == first[0].SHA256 {
|
|
t.Fatalf("changed package was not rehashed: first=%#v second=%#v calls=%d", first, second, hashCalls)
|
|
}
|
|
if err := os.Remove(path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := service.Reconcile(t.Context()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if packages := service.ScanPackages(nil); len(packages) != 0 {
|
|
t.Fatalf("deleted package remains in snapshot: %#v", packages)
|
|
}
|
|
if _, ok, err := service.store.GetReleasePackage(name); err != nil || ok {
|
|
t.Fatalf("deleted package remains in index: ok=%v err=%v", ok, err)
|
|
}
|
|
}
|
|
|
|
func TestScanPackagesNeverHashesDuringRequest(t *testing.T) {
|
|
dir := t.TempDir()
|
|
name := "YMhut_Box_2.2.1_x64.7z"
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte("large package placeholder"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service := NewService(&config.Config{DownloadsDir: dir, BaseURL: "https://update.example"}, nil)
|
|
service.hashFile = func(string) string {
|
|
t.Fatal("request-time package scan attempted to hash file content")
|
|
return ""
|
|
}
|
|
for range 10 {
|
|
if packages := service.ScanPackages(httptest.NewRequest("GET", "https://update.example/api/client/bootstrap", nil)); len(packages) != 1 {
|
|
t.Fatalf("snapshot contains %d packages, want 1", len(packages))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestManifestPrioritizesPublishedVersionOverNumericallyHigherHistoricalPackage(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := &config.Config{
|
|
DownloadsDir: filepath.Join(dir, "downloads"),
|
|
UpdatePublicDir: filepath.Join(dir, "update", "public"),
|
|
BaseURL: "https://update.example",
|
|
}
|
|
if err := os.MkdirAll(cfg.DownloadsDir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.MkdirAll(cfg.UpdatePublicDir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, name := range []string{
|
|
"YMhut_Box_WinUI_Setup_2.0.7.111.exe",
|
|
"YMhut_Box_WinUI_Setup_2.0.7.12.exe",
|
|
} {
|
|
if err := os.WriteFile(filepath.Join(cfg.DownloadsDir, name), []byte(name), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := os.WriteFile(
|
|
filepath.Join(cfg.UpdatePublicDir, "update-info.json"),
|
|
[]byte(`{"app_version":"2.0.7.12"}`),
|
|
0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
service := NewService(cfg, nil)
|
|
manifest := service.Manifest(httptest.NewRequest(http.MethodGet, "https://update.example/api/client/releases", nil))
|
|
if got := manifestString(manifest, "app_version"); got != "2.0.7.12" {
|
|
t.Fatalf("manifest app_version = %q, want 2.0.7.12", got)
|
|
}
|
|
packages, ok := manifest["packages"].([]Package)
|
|
if !ok || len(packages) != 2 {
|
|
t.Fatalf("manifest packages = %#v", manifest["packages"])
|
|
}
|
|
if packages[0].Version != "2.0.7.12" {
|
|
t.Fatalf("first package version = %q, want published version 2.0.7.12", packages[0].Version)
|
|
}
|
|
if got := manifestString(manifest, "download_url"); !strings.Contains(got, "2.0.7.12.exe") {
|
|
t.Fatalf("manifest download_url = %q, want current package", got)
|
|
}
|
|
}
|
|
|
|
func TestManifestFallsBackToHighestPackageWithoutPublishedVersion(t *testing.T) {
|
|
dir := t.TempDir()
|
|
for _, name := range []string{"YMhut_Box_2.0.7.12.exe", "YMhut_Box_2.0.7.111.exe"} {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(name), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
service := NewService(&config.Config{
|
|
DownloadsDir: dir,
|
|
UpdatePublicDir: filepath.Join(dir, "missing-public"),
|
|
BaseURL: "https://update.example",
|
|
}, nil)
|
|
manifest := service.Manifest(httptest.NewRequest(http.MethodGet, "https://update.example/api/client/releases", nil))
|
|
if got := manifestString(manifest, "app_version"); got != "2.0.7.111" {
|
|
t.Fatalf("manifest fallback app_version = %q, want highest indexed package 2.0.7.111", got)
|
|
}
|
|
}
|
|
|
|
func TestSavePreparedPackageRestoresFileAndManifestWhenIndexFails(t *testing.T) {
|
|
service, cfg, closeStore := newPreparedPackageTestService(t)
|
|
name := "YMhut_Box_WinUI_Setup_2.3.0_x64.exe"
|
|
if err := os.MkdirAll(cfg.DownloadsDir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
target := filepath.Join(cfg.DownloadsDir, name)
|
|
if err := os.WriteFile(target, []byte("old package"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
manifestPath := filepath.Join(cfg.UpdatePublicDir, "update-info.json")
|
|
if err := os.MkdirAll(cfg.UpdatePublicDir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
oldManifest := []byte("{\"app_version\":\"1.0.0\"}\n")
|
|
if err := os.WriteFile(manifestPath, oldManifest, 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
temp := filepath.Join(cfg.DownloadsDir, ".upload-new")
|
|
if err := os.WriteFile(temp, []byte("new package"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
closeStore()
|
|
|
|
_, err := service.SavePreparedPackage(
|
|
httptest.NewRequest("POST", "https://update.ymhut.cn/api/admin/releases/packages", nil),
|
|
UploadedPackageFile{TempPath: temp, Size: int64(len("new package")), SHA256: "abc123"},
|
|
UploadOptions{FileName: name, UpdateManifest: true},
|
|
"admin")
|
|
if !errors.Is(err, ErrUploadedPackageIndexFailed) {
|
|
t.Fatalf("got %v, want index failure", err)
|
|
}
|
|
if data, readErr := os.ReadFile(target); readErr != nil || string(data) != "old package" {
|
|
t.Fatalf("package rollback data=%q err=%v", data, readErr)
|
|
}
|
|
if data, readErr := os.ReadFile(manifestPath); readErr != nil || string(data) != string(oldManifest) {
|
|
t.Fatalf("manifest rollback data=%q err=%v", data, readErr)
|
|
}
|
|
}
|
|
|
|
func TestSaveUploadedPackageWritesFileAndUpdatesManifest(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := &config.Config{
|
|
BaseDir: dir,
|
|
StorageDir: filepath.Join(dir, "storage"),
|
|
DataDir: filepath.Join(dir, "data"),
|
|
UpdatePublicDir: filepath.Join(dir, "data", "update", "public"),
|
|
DownloadsDir: filepath.Join(dir, "data", "update", "public", "downloads"),
|
|
BaseURL: "https://update.ymhut.cn",
|
|
Database: config.DatabaseConfig{
|
|
Provider: "sqlite",
|
|
SQLitePath: filepath.Join(dir, "storage", "unified.sqlite"),
|
|
HealthIntervalSec: 30,
|
|
},
|
|
}
|
|
store, err := db.Open(cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
service := NewService(cfg, store)
|
|
req := httptest.NewRequest("POST", "https://update.ymhut.cn/api/admin/releases/packages", nil)
|
|
pkg, err := service.SaveUploadedPackage(req, strings.NewReader("package bytes"), UploadOptions{
|
|
FileName: "YMhut_Box_WinUI_Setup_2.0.6.31.exe",
|
|
UpdateManifest: true,
|
|
}, "admin")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if pkg.Version != "2.0.6.31" || pkg.SHA256 == "" || pkg.Size == 0 {
|
|
t.Fatalf("unexpected package metadata: %#v", pkg)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(cfg.DownloadsDir, pkg.FileName)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
manifest := readJSON(filepath.Join(cfg.UpdatePublicDir, "update-info.json"))
|
|
if manifest["download_url"] != pkg.URL || manifest["package_sha256"] != pkg.SHA256 {
|
|
t.Fatalf("manifest not updated: %#v", manifest)
|
|
}
|
|
}
|
|
|
|
func TestSaveUploadedPackageRejectsUnsafeName(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := &config.Config{
|
|
BaseDir: dir,
|
|
StorageDir: filepath.Join(dir, "storage"),
|
|
DataDir: filepath.Join(dir, "data"),
|
|
UpdatePublicDir: filepath.Join(dir, "data", "update", "public"),
|
|
DownloadsDir: filepath.Join(dir, "data", "update", "public", "downloads"),
|
|
Database: config.DatabaseConfig{
|
|
Provider: "sqlite",
|
|
SQLitePath: filepath.Join(dir, "storage", "unified.sqlite"),
|
|
HealthIntervalSec: 30,
|
|
},
|
|
}
|
|
store, err := db.Open(cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
service := NewService(cfg, store)
|
|
_, err = service.SaveUploadedPackage(httptest.NewRequest("POST", "/", nil), strings.NewReader("x"), UploadOptions{FileName: "../evil.exe"}, "admin")
|
|
if err == nil {
|
|
t.Fatal("expected unsafe filename to be rejected")
|
|
}
|
|
}
|
|
|
|
func TestSavePreparedPackageReplacesExistingFile(t *testing.T) {
|
|
service, cfg, cleanup := newPreparedPackageTestService(t)
|
|
defer cleanup()
|
|
name := "YMhut_Box_WinUI_Setup_2.0.8_x64.exe"
|
|
target := filepath.Join(cfg.DownloadsDir, name)
|
|
if err := os.MkdirAll(cfg.DownloadsDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(target, []byte("old package"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
temp := filepath.Join(cfg.DownloadsDir, ".upload-new")
|
|
if err := os.WriteFile(temp, []byte("new package"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := service.SavePreparedPackage(
|
|
httptest.NewRequest("POST", "https://update.ymhut.cn/api/admin/releases/packages", nil),
|
|
UploadedPackageFile{TempPath: temp, Size: int64(len("new package")), SHA256: "abc123"},
|
|
UploadOptions{FileName: name},
|
|
"admin")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, err := os.ReadFile(target)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(data) != "new package" {
|
|
t.Fatalf("target contains %q, want new package", data)
|
|
}
|
|
backups, err := filepath.Glob(filepath.Join(cfg.DownloadsDir, "."+name+".backup-*"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(backups) != 0 {
|
|
t.Fatalf("successful replacement left backups: %v", backups)
|
|
}
|
|
}
|
|
|
|
func TestSavePreparedPackageRestoresExistingFileWhenManifestFails(t *testing.T) {
|
|
service, cfg, cleanup := newPreparedPackageTestService(t)
|
|
defer cleanup()
|
|
name := "YMhut_Box_WinUI_Setup_2.0.8_x64.exe"
|
|
if err := os.MkdirAll(cfg.DownloadsDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
target := filepath.Join(cfg.DownloadsDir, name)
|
|
if err := os.WriteFile(target, []byte("old package"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
temp := filepath.Join(cfg.DownloadsDir, ".upload-new")
|
|
if err := os.WriteFile(temp, []byte("new package"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
blocked := filepath.Join(cfg.BaseDir, "manifest-blocked")
|
|
if err := os.WriteFile(blocked, []byte("not a directory"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg.UpdatePublicDir = blocked
|
|
|
|
_, err := service.SavePreparedPackage(
|
|
httptest.NewRequest("POST", "https://update.ymhut.cn/api/admin/releases/packages", nil),
|
|
UploadedPackageFile{TempPath: temp, Size: int64(len("new package")), SHA256: "abc123"},
|
|
UploadOptions{FileName: name, UpdateManifest: true},
|
|
"admin")
|
|
if !errors.Is(err, ErrUploadedPackageManifestFailed) {
|
|
t.Fatalf("got %v, want manifest failure", err)
|
|
}
|
|
data, readErr := os.ReadFile(target)
|
|
if readErr != nil {
|
|
t.Fatal(readErr)
|
|
}
|
|
if string(data) != "old package" {
|
|
t.Fatalf("rollback restored %q, want old package", data)
|
|
}
|
|
}
|
|
|
|
func newPreparedPackageTestService(t *testing.T) (*Service, *config.Config, func()) {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
cfg := &config.Config{
|
|
BaseDir: dir,
|
|
StorageDir: filepath.Join(dir, "storage"),
|
|
DataDir: filepath.Join(dir, "data"),
|
|
UpdatePublicDir: filepath.Join(dir, "data", "update", "public"),
|
|
DownloadsDir: filepath.Join(dir, "data", "update", "public", "downloads"),
|
|
BaseURL: "https://update.ymhut.cn",
|
|
Database: config.DatabaseConfig{
|
|
Provider: "sqlite",
|
|
SQLitePath: filepath.Join(dir, "storage", "unified.sqlite"),
|
|
HealthIntervalSec: 30,
|
|
},
|
|
}
|
|
store, err := db.Open(cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return NewService(cfg, store), cfg, func() { _ = store.Close() }
|
|
}
|