test(setup): add fmt.Sprintf argument alignment validation test
Added TestPVESetupScriptArgumentAlignment to prevent future fmt.Sprintf argument mismatch bugs in the PVE quick setup script template. The test uses sentinel values (SENTINEL_URL, SENTINEL_HOST, deadbeef...) to verify that critical placeholders receive the correct argument types: ✓ Repair block INSTALLER_URL uses pulseURL (not authToken) ✓ Repair --pulse-server flags use pulseURL (not authToken) ✓ Authorization headers use runtime $AUTH_TOKEN variable (not hardcoded) ✓ Token ID uses tokenName (pulse-*) (not pulseURL or authToken) This test would have caught the bugs fixed in commits 2bb73d3c7 and 2053bc5e2, where: - authToken appeared in --pulse-server URLs (argument shift) - Authorization headers were hardcoded instead of using runtime variable Recommended by Codex as a safeguard against this class of regression.
This commit is contained in:
parent
708675803f
commit
c9c4a07508
1 changed files with 86 additions and 0 deletions
|
|
@ -45,3 +45,89 @@ func TestHandleSetupScriptRejectsUnsafePulseURL(t *testing.T) {
|
||||||
t.Fatalf("expected 400 bad request for unsafe pulse_url, got %d (%s)", rr.Code, rr.Body.String())
|
t.Fatalf("expected 400 bad request for unsafe pulse_url, got %d (%s)", rr.Code, rr.Body.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPVESetupScriptArgumentAlignment(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
cfg := &config.Config{
|
||||||
|
DataPath: tempDir,
|
||||||
|
ConfigPath: tempDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers := newTestConfigHandlers(t, cfg)
|
||||||
|
|
||||||
|
// Use sentinel values to verify fmt.Sprintf argument alignment
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/api/setup-script?type=pve&host=http://SENTINEL_HOST:8006&pulse_url=http://SENTINEL_URL:7656&auth_token=deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handlers.HandleSetupScript(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200 OK, got %d (%s)", rr.Code, rr.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
script := rr.Body.String()
|
||||||
|
|
||||||
|
// Critical alignment checks to prevent fmt.Sprintf argument mismatch bugs
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
contains string
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "repair_installer_url",
|
||||||
|
contains: `INSTALLER_URL="http://SENTINEL_URL:7656/api/install/install-sensor-proxy.sh"`,
|
||||||
|
desc: "Repair block INSTALLER_URL should get pulseURL, not authToken",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "repair_ctid_pulse_server",
|
||||||
|
contains: `--pulse-server http://SENTINEL_URL:7656`,
|
||||||
|
desc: "Repair --ctid --pulse-server should get pulseURL, not authToken",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "runtime_auth_token_ssh_config",
|
||||||
|
contains: `-H "Authorization: Bearer $AUTH_TOKEN"`,
|
||||||
|
desc: "SSH config Authorization header should use runtime $AUTH_TOKEN variable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token_id_uses_tokenname",
|
||||||
|
contains: `Token ID: pulse-monitor@pam!pulse-`,
|
||||||
|
desc: "Token ID should use tokenName (pulse-*), not pulseURL or authToken",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if !containsString(script, tt.contains) {
|
||||||
|
t.Errorf("%s\nExpected to find: %s\nIn generated script (first 500 chars):\n%s",
|
||||||
|
tt.desc, tt.contains, truncate(script, 500))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional check: ensure authToken doesn't appear in --pulse-server flags
|
||||||
|
if containsString(script, "--pulse-server deadbeef") {
|
||||||
|
t.Error("BUG: authToken appearing in --pulse-server URL (argument misalignment)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsString(s, substr string) bool {
|
||||||
|
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) &&
|
||||||
|
(findSubstring(s, substr) >= 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSubstring(s, substr string) int {
|
||||||
|
for i := 0; i <= len(s)-len(substr); i++ {
|
||||||
|
if s[i:i+len(substr)] == substr {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncate(s string, maxLen int) string {
|
||||||
|
if len(s) <= maxLen {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[:maxLen] + "..."
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue