test(sdk): add zero-cert listener tests for token-only path

cognitive committed Mar 4, 2026 at 22:21 UTC 5c07de47e7323a6bd627b7f5d696fd2bb0b6b6ae
1 file changed +109
sdk/listener_test.go
+109
@@ -8,6 +8,7 @@ import (
8 "net"
9 "net/http"
10 "net/url"
11 + "reflect"
12 "strings"
13 "testing"
14 "time"
@@ -18,6 +19,114 @@ import (
19
20 const testNonTLSStartMarker = byte(0x01)
21
22 +func TestNewListener_ZeroCert_Succeeds(t *testing.T) {
23 + t.Parallel()
24 +
25 + relayAddr := "https://localhost:4017"
26 + lease := &portal.Lease{
27 + ID: "test-lease",
28 + Name: "test-app",
29 + ReverseToken: "test-token",
30 + }
31 + tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12}
32 + zeroCert := tls.Certificate{}
33 +
34 + listener, err := NewListener(relayAddr, lease, tlsConfig, zeroCert, 0, 0)
35 + if err != nil {
36 + t.Fatalf("NewListener with zero cert failed: %v", err)
37 + }
38 + if listener == nil {
39 + t.Fatal("NewListener returned nil listener")
40 + }
41 + defer listener.Close()
42 +
43 + // Use reflection to access unexported controlPlaneCert field
44 + lValue := reflect.ValueOf(listener).Elem()
45 + certField := lValue.FieldByName("controlPlaneCert")
46 + if !certField.IsValid() {
47 + t.Fatal("could not access controlPlaneCert field")
48 + }
49 + // Access the Certificate slice field directly (it's exported from tls.Certificate)
50 + certSlice := certField.FieldByName("Certificate")
51 + if !certSlice.IsValid() {
52 + t.Fatal("could not access Certificate field")
53 + }
54 + if certSlice.Len() != 0 {
55 + t.Fatalf("controlPlaneCert.Certificate has len %d, want 0", certSlice.Len())
56 + }
57 +}
58 +
59 +func TestOpenReverseConnection_NoCert_NoClientCertPresented(t *testing.T) {
60 + t.Parallel()
61 +
62 + // Test the exact condition we care about: when controlPlaneCert is zero-value,
63 + // the TLS config does NOT include the cert. This is what prevents client cert presentation.
64 +
65 + tests := []struct {
66 + cert tls.Certificate
67 + name string
68 + wantCertSet bool
69 + }{
70 + {
71 + name: "zero cert → no client cert in TLS config",
72 + cert: tls.Certificate{},
73 + wantCertSet: false,
74 + },
75 + {
76 + name: "non-zero cert → client cert in TLS config",
77 + cert: tls.Certificate{Certificate: [][]byte{{0x01, 0x02, 0x03}}},
78 + wantCertSet: true,
79 + },
80 + }
81 +
82 + for _, tt := range tests {
83 + t.Run(tt.name, func(t *testing.T) {
84 + t.Parallel()
85 +
86 + l := &Listener{
87 + controlPlaneCert: tt.cert,
88 + }
89 +
90 + // Duplicate the exact logic from listener.go (both HTTP transport and reverse connection)
91 + // First check the HTTP transport logic (lines 134-136)
92 + transportTLSConfig := &tls.Config{
93 + MinVersion: tls.VersionTLS12,
94 + }
95 + if len(l.controlPlaneCert.Certificate) > 0 {
96 + transportTLSConfig.Certificates = []tls.Certificate{l.controlPlaneCert}
97 + }
98 +
99 + if tt.wantCertSet {
100 + if len(transportTLSConfig.Certificates) != 1 {
101 + t.Fatalf("transportTLSConfig.Certificates has len %d, want 1", len(transportTLSConfig.Certificates))
102 + }
103 + } else {
104 + if len(transportTLSConfig.Certificates) != 0 {
105 + t.Fatalf("transportTLSConfig.Certificates has len %d, want 0", len(transportTLSConfig.Certificates))
106 + }
107 + }
108 +
109 + // Then check the reverse connection logic (lines 412-414)
110 + reverseTLSConfig := &tls.Config{
111 + MinVersion: tls.VersionTLS12,
112 + }
113 + if len(l.controlPlaneCert.Certificate) > 0 {
114 + reverseTLSConfig.Certificates = []tls.Certificate{l.controlPlaneCert}
115 + }
116 +
117 + if tt.wantCertSet {
118 + if len(reverseTLSConfig.Certificates) != 1 {
119 + t.Fatalf("reverseTLSConfig.Certificates has len %d, want 1", len(reverseTLSConfig.Certificates))
120 + }
121 + } else {
122 + if len(reverseTLSConfig.Certificates) != 0 {
123 + t.Fatalf("reverseTLSConfig.Certificates has len %d, want 0", len(reverseTLSConfig.Certificates))
124 + }
125 + }
126 + })
127 + }
128 +}
129 +
130 func TestNormalizeRelayAPIURL(t *testing.T) {
131 t.Parallel()
132