check / check (push) Waiting to run
The local index was bound to its destination only through storage_url. With the s3.* form nothing was checked, so after s3.bucket or s3.prefix changed the next backup treated every chunk as already stored, uploaded nothing, and completed a snapshot that could not be restored. Without storage_url, the binding now records the destination the s3.* fields name, written as an s3:// URL from s3.endpoint, s3.bucket and s3.prefix, and a change is refused with the same error and advice. The advice line now names the s3.* fields as well as storage_url. Judgement call: the fields are compared as written, so an equivalent spelling (a trailing slash on the prefix, a scheme on the endpoint) is also refused, as it already is for storage_url. Model: opus-5-5
127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
package vaultik_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/vaultik/internal/config"
|
|
"sneak.berlin/go/vaultik/internal/database"
|
|
"sneak.berlin/go/vaultik/internal/log"
|
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
|
)
|
|
|
|
// buildBindTestVaultik returns a minimal Vaultik wired with a real
|
|
// in-memory DB and a config carrying the given StorageURL. Enough to
|
|
// exercise EnsureStorageBinding without spinning up storage or fx.
|
|
func buildBindTestVaultik(
|
|
t *testing.T, storageURL string,
|
|
) (*vaultik.TestVaultik, *database.Repositories) {
|
|
t.Helper()
|
|
|
|
db, err := database.NewTestDB()
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
|
|
repos := database.NewRepositories(db)
|
|
|
|
tv := vaultik.NewForTesting(nil)
|
|
tv.Repositories = repos
|
|
tv.DB = db
|
|
tv.Config = &config.Config{StorageURL: storageURL}
|
|
tv.SetContext(context.Background())
|
|
|
|
return tv, repos
|
|
}
|
|
|
|
func TestEnsureStorageBinding_FreshDBRecordsURL(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
tv, repos := buildBindTestVaultik(t, "file:///mnt/backups/new")
|
|
|
|
require.NoError(t, tv.EnsureStorageBinding())
|
|
|
|
got, err := repos.LocalMeta.Get(context.Background(), database.LocalMetaKeyStorageURL)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "file:///mnt/backups/new", got,
|
|
"first call must record the configured URL")
|
|
}
|
|
|
|
func TestEnsureStorageBinding_MatchingURLPasses(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
tv, repos := buildBindTestVaultik(t, "s3://bucket/prefix")
|
|
|
|
require.NoError(t, repos.LocalMeta.Set(context.Background(),
|
|
database.LocalMetaKeyStorageURL, "s3://bucket/prefix"))
|
|
|
|
require.NoError(t, tv.EnsureStorageBinding())
|
|
}
|
|
|
|
func TestEnsureStorageBinding_MismatchRefuses(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
tv, repos := buildBindTestVaultik(t, "file:///mnt/backups/new")
|
|
|
|
require.NoError(t, repos.LocalMeta.Set(context.Background(),
|
|
database.LocalMetaKeyStorageURL, "file:///mnt/backups/old"))
|
|
|
|
err := tv.EnsureStorageBinding()
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "file:///mnt/backups/old")
|
|
assert.Contains(t, err.Error(), "file:///mnt/backups/new")
|
|
assert.Contains(t, err.Error(), "vaultik database delete")
|
|
}
|
|
|
|
// A config with no storage_url names its destination with s3.endpoint,
|
|
// s3.bucket and s3.prefix; a change to any of them must be refused just
|
|
// as a changed storage_url is.
|
|
func TestEnsureStorageBinding_S3FieldsChangeRefuses(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
bound := config.S3Config{
|
|
Endpoint: "https://s3.example.com",
|
|
Bucket: "bucket-a",
|
|
Prefix: "backups",
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
changed config.S3Config
|
|
}{
|
|
{"bucket", config.S3Config{
|
|
Endpoint: bound.Endpoint, Bucket: "bucket-b", Prefix: bound.Prefix,
|
|
}},
|
|
{"prefix", config.S3Config{
|
|
Endpoint: bound.Endpoint, Bucket: bound.Bucket, Prefix: "other",
|
|
}},
|
|
{"endpoint", config.S3Config{
|
|
Endpoint: "https://s3.other.com", Bucket: bound.Bucket, Prefix: bound.Prefix,
|
|
}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tv, _ := buildBindTestVaultik(t, "")
|
|
|
|
tv.Config.S3 = bound
|
|
require.NoError(t, tv.EnsureStorageBinding(), "first use binds")
|
|
require.NoError(t, tv.EnsureStorageBinding(), "same destination passes")
|
|
|
|
tv.Config.S3 = tt.changed
|
|
err := tv.EnsureStorageBinding()
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(),
|
|
"local index is bound to a different backup destination")
|
|
assert.Contains(t, err.Error(), "vaultik database delete")
|
|
})
|
|
}
|
|
}
|