220 lines
6.3 KiB
Go
220 lines
6.3 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
// stubObsGetter is a minimal ObservationGetter that returns canned XMPPData
|
|
// and a canned list of related observations.
|
|
type stubObsGetter struct {
|
|
xmpp XMPPData
|
|
related []sdk.RelatedObservation
|
|
relErr error
|
|
}
|
|
|
|
func (s *stubObsGetter) Get(_ context.Context, key sdk.ObservationKey, dest any) error {
|
|
if key != ObservationKeyXMPP {
|
|
return nil
|
|
}
|
|
b, _ := json.Marshal(s.xmpp)
|
|
return json.Unmarshal(b, dest)
|
|
}
|
|
|
|
func (s *stubObsGetter) GetRelated(_ context.Context, _ sdk.ObservationKey) ([]sdk.RelatedObservation, error) {
|
|
return s.related, s.relErr
|
|
}
|
|
|
|
func mkTLSObs(t *testing.T, payload any) sdk.RelatedObservation {
|
|
t.Helper()
|
|
b, err := json.Marshal(payload)
|
|
if err != nil {
|
|
t.Fatalf("marshal tls payload: %v", err)
|
|
}
|
|
return sdk.RelatedObservation{
|
|
CheckerID: "tls",
|
|
Key: TLSRelatedKey,
|
|
Data: b,
|
|
CollectedAt: time.Now(),
|
|
Ref: "ep-1",
|
|
}
|
|
}
|
|
|
|
func TestRule_FoldsTLSCritIntoAggregate(t *testing.T) {
|
|
obs := &stubObsGetter{
|
|
xmpp: healthyXMPPData(),
|
|
related: []sdk.RelatedObservation{
|
|
mkTLSObs(t, map[string]any{
|
|
"host": "xmpp.example.com",
|
|
"port": 5222,
|
|
"chain_valid": false,
|
|
"hostname_match": true,
|
|
}),
|
|
},
|
|
}
|
|
states := (&xmppRule{}).Evaluate(context.Background(), obs, sdk.CheckerOptions{"domain": "example.com", "mode": "both"})
|
|
state := states[0]
|
|
if state.Status != sdk.StatusCrit {
|
|
t.Fatalf("expected StatusCrit due to TLS chain invalid, got %s (%s)", state.Status, state.Message)
|
|
}
|
|
if !strings.Contains(state.Message, "xmpp.example.com:5222") && !strings.Contains(state.Message, "Invalid certificate") {
|
|
t.Fatalf("expected TLS message in state, got %q", state.Message)
|
|
}
|
|
}
|
|
|
|
func TestRule_IgnoresUnrelatedTLSObs(t *testing.T) {
|
|
obs := &stubObsGetter{
|
|
xmpp: healthyXMPPData(),
|
|
related: nil,
|
|
}
|
|
states := (&xmppRule{}).Evaluate(context.Background(), obs, sdk.CheckerOptions{"domain": "example.com", "mode": "both"})
|
|
state := states[0]
|
|
if state.Status != sdk.StatusOK {
|
|
t.Fatalf("expected StatusOK without related TLS issues, got %s (%s)", state.Status, state.Message)
|
|
}
|
|
}
|
|
|
|
func TestHTMLReportCtx_IncludesTLSPosture(t *testing.T) {
|
|
data := healthyXMPPData()
|
|
p := &xmppProvider{}
|
|
related := []sdk.RelatedObservation{
|
|
mkTLSObs(t, map[string]any{
|
|
"host": "xmpp.example.com",
|
|
"port": 5222,
|
|
"chain_valid": true,
|
|
"hostname_match": true,
|
|
"not_after": time.Now().Add(60 * 24 * time.Hour).Format(time.RFC3339),
|
|
"tls_version": "TLS 1.3",
|
|
}),
|
|
}
|
|
rctx := &stubReportCtx{data: mustJSON(t, data), related: related}
|
|
html, err := p.GetHTMLReport(rctx)
|
|
if err != nil {
|
|
t.Fatalf("GetHTMLReport: %v", err)
|
|
}
|
|
if !strings.Contains(html, "chain valid") {
|
|
t.Fatal("expected 'chain valid' in HTML, not found")
|
|
}
|
|
if !strings.Contains(html, "hostname match") {
|
|
t.Fatal("expected 'hostname match' in HTML, not found")
|
|
}
|
|
if !strings.Contains(html, "TLS checker") {
|
|
t.Fatal("expected TLS checker footer mention, not found")
|
|
}
|
|
}
|
|
|
|
func TestHTMLReport_BackCompatNoRelated(t *testing.T) {
|
|
data := healthyXMPPData()
|
|
p := &xmppProvider{}
|
|
// StaticReportContext mimics the host-side "no related observations" path
|
|
// (e.g. /report HTTP handler on the remote checker).
|
|
html, err := p.GetHTMLReport(sdk.StaticReportContext(mustJSON(t, data)))
|
|
if err != nil {
|
|
t.Fatalf("GetHTMLReport: %v", err)
|
|
}
|
|
// Renderer must still produce a valid document and must not include TLS
|
|
// posture rows when no related observations were passed.
|
|
if !strings.Contains(html, "<title>XMPP Report") {
|
|
t.Fatal("expected report title in HTML")
|
|
}
|
|
if strings.Contains(html, "TLS cert") {
|
|
t.Fatal("did not expect 'TLS cert' row without related observations")
|
|
}
|
|
}
|
|
|
|
type stubReportCtx struct {
|
|
data json.RawMessage
|
|
related []sdk.RelatedObservation
|
|
}
|
|
|
|
func (s *stubReportCtx) Data() json.RawMessage { return s.data }
|
|
func (s *stubReportCtx) Related(_ sdk.ObservationKey) []sdk.RelatedObservation {
|
|
return s.related
|
|
}
|
|
func (s *stubReportCtx) States() []sdk.CheckState { return nil }
|
|
|
|
func mustJSON(t *testing.T, v any) json.RawMessage {
|
|
t.Helper()
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func healthyXMPPData() XMPPData {
|
|
return XMPPData{
|
|
Domain: "example.com",
|
|
SRV: SRVLookup{
|
|
Client: []SRVRecord{{Target: "xmpp.example.com", Port: 5222}},
|
|
Server: []SRVRecord{{Target: "xmpp.example.com", Port: 5269}},
|
|
},
|
|
Endpoints: []EndpointProbe{
|
|
{
|
|
Mode: ModeClient, Target: "xmpp.example.com", Port: 5222,
|
|
Address: "xmpp.example.com:5222",
|
|
TCPConnected: true,
|
|
StreamOpened: true,
|
|
STARTTLSOffered: true,
|
|
STARTTLSRequired: true,
|
|
STARTTLSUpgraded: true,
|
|
FeaturesRead: true,
|
|
SASLMechanisms: []string{"SCRAM-SHA-256", "SCRAM-SHA-256-PLUS"},
|
|
},
|
|
{
|
|
Mode: ModeServer, Target: "xmpp.example.com", Port: 5269,
|
|
Address: "xmpp.example.com:5269",
|
|
TCPConnected: true,
|
|
StreamOpened: true,
|
|
STARTTLSOffered: true,
|
|
STARTTLSRequired: true,
|
|
STARTTLSUpgraded: true,
|
|
FeaturesRead: true,
|
|
DialbackOffered: true,
|
|
},
|
|
},
|
|
Coverage: ReachabilitySpan{HasIPv4: true, WorkingC2S: true, WorkingS2S: true},
|
|
}
|
|
}
|
|
|
|
func TestTLSIssuesFromRelated_StructuredIssues(t *testing.T) {
|
|
related := []sdk.RelatedObservation{
|
|
mkTLSObs(t, map[string]any{
|
|
"host": "xmpp.example.com",
|
|
"port": 5222,
|
|
"issues": []map[string]any{
|
|
{"code": "tls.self_signed", "severity": "crit", "message": "self-signed cert"},
|
|
{"code": "tls.weak_cipher", "severity": "warn", "message": "weak cipher"},
|
|
},
|
|
}),
|
|
}
|
|
out := tlsIssuesFromRelated(related)
|
|
if len(out) != 2 {
|
|
t.Fatalf("expected 2 issues, got %d", len(out))
|
|
}
|
|
if out[0].Code != "xmpp.tls.self_signed" || out[0].Severity != SeverityCrit {
|
|
t.Fatalf("unexpected first issue: %+v", out[0])
|
|
}
|
|
}
|
|
|
|
func TestTLSIssuesFromRelated_FlagsOnly(t *testing.T) {
|
|
related := []sdk.RelatedObservation{
|
|
mkTLSObs(t, map[string]any{
|
|
"host": "xmpp.example.com",
|
|
"port": 5222,
|
|
"hostname_match": false,
|
|
}),
|
|
}
|
|
out := tlsIssuesFromRelated(related)
|
|
if len(out) != 1 {
|
|
t.Fatalf("expected 1 synthesized issue, got %d", len(out))
|
|
}
|
|
if out[0].Severity != SeverityCrit || !strings.Contains(out[0].Message, "does not cover") {
|
|
t.Fatalf("unexpected synthesized issue: %+v", out[0])
|
|
}
|
|
}
|