R-1271 (P2): „use my kept data” never takes a copy dated in the future — a copy more than 10 minutes past the box's clock never counts as the newest (the newest true date wins, tie local); alone it is still offered, said and not recommended. Red-proved. Commit only.
gates / gates (push) Successful in 1m33s
gates / gates (push) Successful in 1m33s
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
)
|
||||
|
||||
// R-1271 (brief 2026-10-25 Part 4) — a future-dated off-site copy, the ONLY copy of the kept files: still offered (it
|
||||
// may be the household's), but the question says its date is in the future and does not recommend it. Same rig as
|
||||
// TestR691 (the restic runner seam; nothing reaches restic, ssh or Docker).
|
||||
// COMPANION RED-PROOF (REPORT.md): with keptPick ignoring the future date, use_future stays false and this fails.
|
||||
func TestR1271_AFutureCopyAloneIsSaidAndNotRecommended(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
drive := filepath.Join(dir, "drive")
|
||||
cfg := &config.Config{}
|
||||
cfg.Paths.StacksDir = filepath.Join(dir, "stacks")
|
||||
cfg.Paths.DataDir = filepath.Join(dir, "data")
|
||||
cfg.Stacks.ComposeCommand = "docker compose"
|
||||
app := filepath.Join(cfg.Paths.StacksDir, "cloudapp")
|
||||
for _, d := range []string{app, filepath.Join(drive, "appdata/cloudapp"), cfg.Paths.DataDir} {
|
||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
_ = os.WriteFile(filepath.Join(app, "docker-compose.yml"), []byte("services:\n cloudapp:\n image: busybox\n volumes:\n - ${HDD_PATH}/appdata/cloudapp:/data\n"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(app, ".felhom.yml"), []byte("display_name: Cloud App\n"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(drive, "appdata/cloudapp/old.txt"), []byte("old"), 0o644)
|
||||
m, err := stacks.NewManager(cfg, log.New(io.Discard, "", 0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := m.ScanStacks(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sett, err := settings.Load(filepath.Join(cfg.Paths.DataDir, "settings.json"), log.New(io.Discard, "", 0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := sett.SetOffboxTarget(&settings.OffboxTarget{Enabled: true, Host: "nas.local", Port: 22, User: "felhom",
|
||||
RepoPath: "/srv/repo", Schedule: "daily", EscrowState: "escrowed"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bm := backup.NewManager(cfg, sett, log.New(io.Discard, "", 0))
|
||||
if err := bm.WriteOffboxSecrets("KEY", "nas.local ssh-ed25519 AAAA"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snapAt := time.Now().Add(4 * 24 * time.Hour) // the rehearsal box's clock: four days ahead
|
||||
bm.SetOffboxRunner(func(_ context.Context, _ []string, args ...string) ([]byte, error) {
|
||||
for _, a := range args {
|
||||
if a == "snapshots" {
|
||||
return json.Marshal([]map[string]interface{}{{"short_id": "abba81ba", "time": snapAt,
|
||||
"tags": []string{"cloudapp"}, "paths": []string{drive + "/backups/primary/cloudapp", drive + "/appdata/cloudapp"}}})
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
r := &Router{stackMgr: m, backupMgr: bm, logger: log.New(io.Discard, "", 0)}
|
||||
for lang, want := range map[string]string{"hu": "a jövőben van", "en": "is in the future"} {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/stacks/cloudapp/deploy?lang="+lang, nil)
|
||||
if !r.keptDataAtInstall(w, req, "cloudapp", drive, "") || w.Code != http.StatusConflict {
|
||||
t.Fatalf("%s: the choice was not asked (code %d)", lang, w.Code)
|
||||
}
|
||||
var body map[string]interface{}
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &body)
|
||||
data, _ := body["data"].(map[string]interface{})
|
||||
warn, _ := data["use_warn"].(string)
|
||||
if data["use_offered"] != true || data["use_future"] != true || !strings.Contains(warn, want) {
|
||||
t.Fatalf("%s: a future-dated only copy is offered, marked and said: offered=%v future=%v warn=%q", lang, data["use_offered"], data["use_future"], warn)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user