continuous-integration/drone Build is failing
Migrate the workspace to React and Ant Design, improve account forms and responsive layouts, support outbound diagnostics and SSH fingerprint confirmation, and persist the management startup port. Preserve database migrations, remove S3 settings storage, and improve gateway audit handling.
121 lines
3.9 KiB
Go
121 lines
3.9 KiB
Go
package store
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"linkport/internal/model"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func backupAsset() model.Asset {
|
|
return model.Asset{ID: "cluster-asset", Name: "cluster", Type: "kubeconfig", Content: `apiVersion: v1
|
|
kind: Config
|
|
current-context: test
|
|
clusters:
|
|
- name: cluster
|
|
cluster:
|
|
server: https://cluster.invalid
|
|
contexts:
|
|
- name: test
|
|
context:
|
|
cluster: cluster
|
|
user: user
|
|
users:
|
|
- name: user
|
|
user:
|
|
token: asset-private-token
|
|
`}
|
|
}
|
|
|
|
func TestAssetBackupRoundTripAndRedaction(t *testing.T) {
|
|
s := configStore(t)
|
|
a := backupAsset()
|
|
o := model.OutboundProfile{ID: "kube", Name: "kube", Outbound: model.Outbound{Mode: "k8s", Resource: "pod/db", KubeConfigAssetID: a.ID}}
|
|
in := Bundle{Version: 2, Assets: []model.Asset{a}, Outbounds: []model.OutboundProfile{o}}
|
|
if err := s.RestoreBundle(in); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
template, err := s.SaveTemplate("asset template", in)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, _ := json.Marshal(template)
|
|
if bytes.Contains(raw, []byte("asset-private-token")) || a.Content == "" {
|
|
t.Fatal("template leaked or mutated asset")
|
|
}
|
|
backup, err := s.Backup("fixture-long-passphrase")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
restored, err := OpenBackup(backup, "fixture-long-passphrase")
|
|
if err != nil || len(restored.Assets) != 1 || restored.Assets[0].Content != a.Content || len(restored.Outbounds) != 1 || restored.Outbounds[0].Outbound.KubeConfigAssetID != a.ID {
|
|
t.Fatal("backup lost asset content or reference", err)
|
|
}
|
|
dest := configStore(t)
|
|
if err := dest.RestoreBundle(restored); err != nil {
|
|
t.Fatal("cannot restore asset and outbound atomically", err)
|
|
}
|
|
got, err := dest.GetAsset(a.ID)
|
|
if err != nil || got.Content != a.Content {
|
|
t.Fatal("restored asset mismatch", err)
|
|
}
|
|
}
|
|
|
|
func TestAssetImportPreviewHistoryAndRevisionGuard(t *testing.T) {
|
|
s := configStore(t)
|
|
a := backupAsset()
|
|
if err := s.RestoreBundle(Bundle{Version: 2, Assets: []model.Asset{a}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a.Content = strings.ReplaceAll(a.Content, "asset-private-token", "replacement-private-token")
|
|
in := Bundle{Version: 2, Assets: []model.Asset{a}}
|
|
preview, err := s.PreviewBundle(in)
|
|
if err != nil || preview.Assets != 1 || len(preview.Resources) != 1 {
|
|
t.Fatal("asset preview missing", err)
|
|
}
|
|
raw, _ := json.Marshal(preview)
|
|
if bytes.Contains(raw, []byte("private-token")) || len(preview.Resources[0].Changes) != 1 || !preview.Resources[0].Changes[0].SecretChanged {
|
|
t.Fatal("asset preview leaked content or omitted secret change", string(raw))
|
|
}
|
|
if err := s.RestoreBundleChecked(in, preview.Revision); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.RestoreBundleChecked(in, preview.Revision); !errors.Is(err, ErrConfigurationChanged) {
|
|
t.Fatal("asset update failed to invalidate preview", err)
|
|
}
|
|
diff, err := s.ConfigurationDiff("assets", a.ID, 1)
|
|
if err != nil || len(diff) != 1 || !diff[0].SecretChanged || diff[0].Before != nil || diff[0].After != nil {
|
|
t.Fatal("asset history content leaked", err)
|
|
}
|
|
if err := s.RestoreRevision("assets", a.ID, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := s.GetAsset(a.ID)
|
|
if err != nil || got.Content != backupAsset().Content {
|
|
t.Fatal("asset history restore failed", err)
|
|
}
|
|
}
|
|
|
|
func TestAssetImportRejectsBrokenReferencesAtomically(t *testing.T) {
|
|
s := configStore(t)
|
|
a := backupAsset()
|
|
o := model.OutboundProfile{ID: "kube", Name: "kube", Outbound: model.Outbound{Mode: "k8s", Resource: "pod/db", KubeConfigAssetID: "missing"}}
|
|
in := Bundle{Version: 2, Assets: []model.Asset{a}, Outbounds: []model.OutboundProfile{o}}
|
|
if _, err := s.PreviewBundle(in); err == nil {
|
|
t.Fatal("preview accepted missing asset")
|
|
}
|
|
if err := s.RestoreBundle(in); err == nil {
|
|
t.Fatal("import accepted missing asset")
|
|
}
|
|
if _, err := s.GetAsset(a.ID); err == nil {
|
|
t.Fatal("invalid import partially committed")
|
|
}
|
|
in.Outbounds = nil
|
|
in.Assets = append(in.Assets, a)
|
|
if err := s.RestoreBundle(in); err == nil {
|
|
t.Fatal("import accepted duplicate assets")
|
|
}
|
|
}
|