Accountant: Inventar, where, Checkmk, Abrechnung
This commit is contained in:
1 parent
9f16148cde
commit
dd93277750
59 files changed
+7522
-113
No files matched your search
@@ -0,0 +1,144 @@
|
||||
package accountant
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"sultan/pkg/auth"
|
||||
"sultan/pkg/crypto"
|
||||
"sultan/pkg/ledger"
|
||||
)
|
||||
|
||||
// enrolledPayee registriert einen Payee und liefert seinen Schlüssel.
|
||||
func enrolledPayee(t *testing.T, a *Accountant, id string) ed25519.PrivateKey {
|
||||
t.Helper()
|
||||
key, _ := auth.GenerateKey()
|
||||
tok := invite(t, a, id)
|
||||
if resp := enroll(t, a, key, EnrollRequest{Token: tok.Secret, Hostname: id + ".lan"}); resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("Enrollment: %d", resp.StatusCode)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func postReport(t *testing.T, a *Accountant, key ed25519.PrivateKey, body []byte) *http.Response {
|
||||
t.Helper()
|
||||
req, _ := http.NewRequest(http.MethodPost, "https://"+a.Addr()+PathReport, bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if key != nil {
|
||||
if err := (&auth.Signer{Key: key}).Sign(req); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
hc := &http.Client{Transport: &http.Transport{TLSClientConfig: crypto.PinnedTLSConfig(a.Fingerprint())}}
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { resp.Body.Close() })
|
||||
return resp
|
||||
}
|
||||
|
||||
func latest(t *testing.T, a *Accountant, payeeID string) (Report, time.Time, bool) {
|
||||
t.Helper()
|
||||
l, err := ledger.Open(ctx, filepath.Join(a.DataDir(), "accountant.db"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
body, at, err := l.LatestReport(ctx, payeeID)
|
||||
if err != nil {
|
||||
return Report{}, time.Time{}, false
|
||||
}
|
||||
var r Report
|
||||
if err := json.Unmarshal(body, &r); err != nil {
|
||||
t.Fatalf("gespeicherter Bericht unlesbar: %v", err)
|
||||
}
|
||||
return r, at, true
|
||||
}
|
||||
|
||||
func sampleReport() Report {
|
||||
return Report{
|
||||
Version: "v0.1.0",
|
||||
GeneratedAt: time.Date(2026, 10, 17, 9, 0, 0, 0, time.UTC),
|
||||
Tenants: []ReportTenant{{ID: "kunde", Name: "Kunde A", Tier: "pro", Status: "active"}},
|
||||
Nodes: []ReportNode{{ID: "webdocker11", Tenant: "kunde", Hostname: "webdocker11", FQDN: "webdocker11.lan.r25dc.de",
|
||||
IP: "10.0.0.11", Status: "online", Connected: true}},
|
||||
Routes: []ReportRoute{{Tenant: "kunde", Node: "webdocker11", Workload: "kunde-a-wordpress-1", Router: "wp",
|
||||
Protocol: "http", Hosts: []string{"kunde.de"}, Backend: "http://10.0.0.11:8081",
|
||||
BackendFQDN: "webdocker11.lan.r25dc.de:8081", Active: true, ComposeDir: "/srv/kunde-a"}},
|
||||
}
|
||||
}
|
||||
|
||||
// Ein registrierter Payee meldet seinen Schnappschuss; gespeichert wird der
|
||||
// letzte.
|
||||
func TestReport_StoresLatest(t *testing.T) {
|
||||
a := start(t, "")
|
||||
key := enrolledPayee(t, a, "webproxy11")
|
||||
first := sampleReport()
|
||||
body, _ := json.Marshal(first)
|
||||
if resp := postReport(t, a, key, body); resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("Status %d", resp.StatusCode)
|
||||
}
|
||||
got, _, ok := latest(t, a, "webproxy11")
|
||||
if !ok || got.Routes[0].BackendFQDN != "webdocker11.lan.r25dc.de:8081" || got.Nodes[0].FQDN != "webdocker11.lan.r25dc.de" {
|
||||
t.Errorf("gespeichert: %+v", got)
|
||||
}
|
||||
|
||||
second := sampleReport()
|
||||
second.Routes = nil
|
||||
body, _ = json.Marshal(second)
|
||||
postReport(t, a, key, body)
|
||||
if got, _, _ := latest(t, a, "webproxy11"); len(got.Routes) != 0 {
|
||||
t.Errorf("Bericht nicht ersetzt: %+v", got.Routes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReport_Errors(t *testing.T) {
|
||||
a := start(t, "")
|
||||
key := enrolledPayee(t, a, "webproxy11")
|
||||
stranger, _ := auth.GenerateKey()
|
||||
valid, _ := json.Marshal(sampleReport())
|
||||
tests := []struct {
|
||||
name string
|
||||
key ed25519.PrivateKey
|
||||
body []byte
|
||||
want int
|
||||
}{
|
||||
{"unsigniert", nil, valid, http.StatusUnauthorized},
|
||||
{"nicht registrierter Schlüssel", stranger, valid, http.StatusUnauthorized},
|
||||
{"kein JSON", key, []byte("kaputt"), http.StatusBadRequest},
|
||||
{"kein Zeitpunkt", key, []byte(`{"tenants":[]}`), http.StatusBadRequest},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if resp := postReport(t, a, tt.key, tt.body); resp.StatusCode != tt.want {
|
||||
t.Errorf("Status %d, erwartet %d", resp.StatusCode, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
if _, _, ok := latest(t, a, "webproxy11"); ok {
|
||||
t.Error("fehlerhafter Bericht gespeichert")
|
||||
}
|
||||
}
|
||||
|
||||
// Große Hoster: Berichte bis 8 MiB werden angenommen.
|
||||
func TestReport_LargeBody(t *testing.T) {
|
||||
a := start(t, "")
|
||||
key := enrolledPayee(t, a, "webproxy11")
|
||||
r := sampleReport()
|
||||
for i := 0; i < 6000; i++ {
|
||||
r.Routes = append(r.Routes, r.Routes[0])
|
||||
}
|
||||
body, _ := json.Marshal(r)
|
||||
if len(body) < 1<<20 {
|
||||
t.Fatalf("Testbericht nur %d Bytes", len(body))
|
||||
}
|
||||
if resp := postReport(t, a, key, body); resp.StatusCode != http.StatusNoContent {
|
||||
t.Errorf("großer Bericht: Status %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user