Skip to content

Commit cd1bd43

Browse files
committed
test: expand unit test coverage for untested controllers
Add unit tests for controllers that previously had zero test coverage: - followerchannel: validation tests (6 cases) - console: validation + GetConfig mapping tests (10 cases) - chaincode/install: CCAAS package generation tests (3 cases) - chaincode/deploy: resource naming tests (8 cases) - mainchannel: validation tests (10 cases) with ConfigValidator refactor Extend existing CA controller tests with regression tests for issue #214: - TestGetDNSNames: DNS name extraction from mixed host/IP lists (6 cases) - TestGetIPAddresses: IP extraction with localhost always included (5 cases) - TestDoesCertNeedsToBeRenewed: certificate renewal detection (6 cases) Total: 54 new test cases across 7 files. All tests pass with `go test ./controllers/... -short`. Signed-off-by: David Viejo <dviejo@kungfusoftware.es>
1 parent 50cf5ef commit cd1bd43

File tree

7 files changed

+1200
-0
lines changed

7 files changed

+1200
-0
lines changed

controllers/ca/ca_dns_ip_test.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package ca
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"crypto/rand"
7+
"crypto/x509"
8+
"math/big"
9+
"net"
10+
"testing"
11+
"time"
12+
13+
hlfv1alpha1 "github.com/kfsoftware/hlf-operator/pkg/apis/hlf.kungfusoftware.es/v1alpha1"
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
)
18+
19+
// Regression tests for issue #214: x509 certificate valid for wrong IP
20+
21+
func TestGetDNSNames(t *testing.T) {
22+
tests := []struct {
23+
name string
24+
hosts []string
25+
expected []string
26+
}{
27+
{
28+
name: "Only hostnames",
29+
hosts: []string{"ca.example.com", "ca.org1.example.com"},
30+
expected: []string{"ca.example.com", "ca.org1.example.com"},
31+
},
32+
{
33+
name: "Only IPs are excluded",
34+
hosts: []string{"192.168.1.1", "10.0.0.1"},
35+
expected: nil,
36+
},
37+
{
38+
name: "Mixed hostnames and IPs",
39+
hosts: []string{"ca.example.com", "192.168.1.1", "orderer.example.com", "10.0.0.1"},
40+
expected: []string{"ca.example.com", "orderer.example.com"},
41+
},
42+
{
43+
name: "Empty hosts",
44+
hosts: []string{},
45+
expected: nil,
46+
},
47+
{
48+
name: "IPv6 addresses are excluded",
49+
hosts: []string{"ca.example.com", "::1", "fe80::1"},
50+
expected: []string{"ca.example.com"},
51+
},
52+
{
53+
name: "Localhost hostname is kept as DNS name",
54+
hosts: []string{"localhost"},
55+
expected: []string{"localhost"},
56+
},
57+
}
58+
59+
for _, tt := range tests {
60+
t.Run(tt.name, func(t *testing.T) {
61+
t.Parallel()
62+
spec := hlfv1alpha1.FabricCASpec{Hosts: tt.hosts}
63+
result := getDNSNames(spec)
64+
assert.Equal(t, tt.expected, result)
65+
})
66+
}
67+
}
68+
69+
func TestGetIPAddresses(t *testing.T) {
70+
tests := []struct {
71+
name string
72+
hosts []string
73+
expected []net.IP
74+
}{
75+
{
76+
name: "No hosts still includes localhost",
77+
hosts: []string{},
78+
expected: []net.IP{net.ParseIP("127.0.0.1")},
79+
},
80+
{
81+
name: "Only hostnames - only localhost returned",
82+
hosts: []string{"ca.example.com"},
83+
expected: []net.IP{net.ParseIP("127.0.0.1")},
84+
},
85+
{
86+
name: "IPv4 addresses are included",
87+
hosts: []string{"192.168.1.1", "10.0.0.1"},
88+
expected: []net.IP{
89+
net.ParseIP("127.0.0.1"),
90+
net.ParseIP("192.168.1.1"),
91+
net.ParseIP("10.0.0.1"),
92+
},
93+
},
94+
{
95+
name: "Mixed hosts - only IPs extracted",
96+
hosts: []string{"ca.example.com", "192.168.1.1", "orderer.example.com"},
97+
expected: []net.IP{
98+
net.ParseIP("127.0.0.1"),
99+
net.ParseIP("192.168.1.1"),
100+
},
101+
},
102+
{
103+
name: "IPv6 addresses are included",
104+
hosts: []string{"::1", "fe80::1"},
105+
expected: []net.IP{
106+
net.ParseIP("127.0.0.1"),
107+
net.ParseIP("::1"),
108+
net.ParseIP("fe80::1"),
109+
},
110+
},
111+
}
112+
113+
for _, tt := range tests {
114+
t.Run(tt.name, func(t *testing.T) {
115+
t.Parallel()
116+
spec := hlfv1alpha1.FabricCASpec{Hosts: tt.hosts}
117+
result := getIPAddresses(spec)
118+
assert.Equal(t, tt.expected, result)
119+
})
120+
}
121+
}
122+
123+
func TestDoesCertNeedsToBeRenewed(t *testing.T) {
124+
tests := []struct {
125+
name string
126+
certDNSNames []string
127+
certIPs []net.IP
128+
specHosts []string
129+
expectRenewal bool
130+
}{
131+
{
132+
name: "Cert matches spec - no renewal needed",
133+
certDNSNames: []string{"ca.example.com"},
134+
certIPs: []net.IP{net.ParseIP("127.0.0.1")},
135+
specHosts: []string{"ca.example.com"},
136+
expectRenewal: false,
137+
},
138+
{
139+
name: "DNS name added to spec - renewal needed",
140+
certDNSNames: []string{"ca.example.com"},
141+
certIPs: []net.IP{net.ParseIP("127.0.0.1")},
142+
specHosts: []string{"ca.example.com", "ca2.example.com"},
143+
expectRenewal: true,
144+
},
145+
{
146+
name: "DNS name removed from spec - renewal needed",
147+
certDNSNames: []string{"ca.example.com", "ca2.example.com"},
148+
certIPs: []net.IP{net.ParseIP("127.0.0.1")},
149+
specHosts: []string{"ca.example.com"},
150+
expectRenewal: true,
151+
},
152+
{
153+
name: "IP added to spec - DNS unchanged - no renewal",
154+
certDNSNames: []string{"ca.example.com"},
155+
certIPs: []net.IP{net.ParseIP("127.0.0.1")},
156+
specHosts: []string{"ca.example.com", "192.168.1.1"},
157+
expectRenewal: false, // function only checks DNS names, not IPs
158+
},
159+
{
160+
name: "Order differs but same DNS names - no renewal",
161+
certDNSNames: []string{"b.example.com", "a.example.com"},
162+
certIPs: []net.IP{net.ParseIP("127.0.0.1")},
163+
specHosts: []string{"a.example.com", "b.example.com"},
164+
expectRenewal: false, // function sorts before comparing
165+
},
166+
{
167+
name: "Empty cert DNS, empty spec hosts - no renewal",
168+
certDNSNames: nil,
169+
certIPs: []net.IP{net.ParseIP("127.0.0.1")},
170+
specHosts: []string{},
171+
expectRenewal: false,
172+
},
173+
}
174+
175+
for _, tt := range tests {
176+
t.Run(tt.name, func(t *testing.T) {
177+
t.Parallel()
178+
179+
// Create a minimal x509 certificate with the test DNS/IP values
180+
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
181+
require.NoError(t, err)
182+
183+
template := &x509.Certificate{
184+
SerialNumber: big.NewInt(1),
185+
NotBefore: time.Now().Add(-1 * time.Hour),
186+
NotAfter: time.Now().Add(24 * time.Hour),
187+
DNSNames: tt.certDNSNames,
188+
IPAddresses: tt.certIPs,
189+
}
190+
191+
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey)
192+
require.NoError(t, err)
193+
194+
cert, err := x509.ParseCertificate(certBytes)
195+
require.NoError(t, err)
196+
197+
conf := &hlfv1alpha1.FabricCA{
198+
ObjectMeta: v1.ObjectMeta{
199+
Name: "test-ca",
200+
Namespace: "test-ns",
201+
},
202+
Spec: hlfv1alpha1.FabricCASpec{
203+
Hosts: tt.specHosts,
204+
},
205+
}
206+
207+
result := doesCertNeedsToBeRenewed(cert, conf)
208+
assert.Equal(t, tt.expectRenewal, result)
209+
})
210+
}
211+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package deploy
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
hlfv1alpha1 "github.com/kfsoftware/hlf-operator/pkg/apis/hlf.kungfusoftware.es/v1alpha1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
)
11+
12+
func newFabricChaincode(name string) *hlfv1alpha1.FabricChaincode {
13+
return &hlfv1alpha1.FabricChaincode{
14+
ObjectMeta: metav1.ObjectMeta{
15+
Name: name,
16+
},
17+
}
18+
}
19+
20+
func TestResourceNaming(t *testing.T) {
21+
r := &FabricChaincodeDeployReconciler{}
22+
23+
tests := []struct {
24+
name string
25+
chaincodeName string
26+
wantDeploymentName string
27+
wantServiceName string
28+
wantSecretName string
29+
}{
30+
{
31+
name: "normal name",
32+
chaincodeName: "my-chaincode",
33+
wantDeploymentName: "my-chaincode",
34+
wantServiceName: "my-chaincode",
35+
wantSecretName: "my-chaincode-certs",
36+
},
37+
{
38+
name: "name with dots",
39+
chaincodeName: "org1.chaincode.v1",
40+
wantDeploymentName: "org1.chaincode.v1",
41+
wantServiceName: "org1.chaincode.v1",
42+
wantSecretName: "org1.chaincode.v1-certs",
43+
},
44+
{
45+
name: "name with underscores",
46+
chaincodeName: "my_chaincode_v2",
47+
wantDeploymentName: "my_chaincode_v2",
48+
wantServiceName: "my_chaincode_v2",
49+
wantSecretName: "my_chaincode_v2-certs",
50+
},
51+
{
52+
name: "long name",
53+
chaincodeName: strings.Repeat("a", 63),
54+
wantDeploymentName: strings.Repeat("a", 63),
55+
wantServiceName: strings.Repeat("a", 63),
56+
wantSecretName: fmt.Sprintf("%s-certs", strings.Repeat("a", 63)),
57+
},
58+
{
59+
name: "single character name",
60+
chaincodeName: "x",
61+
wantDeploymentName: "x",
62+
wantServiceName: "x",
63+
wantSecretName: "x-certs",
64+
},
65+
}
66+
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
fc := newFabricChaincode(tt.chaincodeName)
70+
71+
gotDeployment := r.getDeploymentName(fc)
72+
if gotDeployment != tt.wantDeploymentName {
73+
t.Errorf("getDeploymentName() = %q, want %q", gotDeployment, tt.wantDeploymentName)
74+
}
75+
76+
gotService := r.getServiceName(fc)
77+
if gotService != tt.wantServiceName {
78+
t.Errorf("getServiceName() = %q, want %q", gotService, tt.wantServiceName)
79+
}
80+
81+
gotSecret := r.getSecretName(fc)
82+
if gotSecret != tt.wantSecretName {
83+
t.Errorf("getSecretName() = %q, want %q", gotSecret, tt.wantSecretName)
84+
}
85+
})
86+
}
87+
}
88+
89+
func TestResourceNamingConsistency(t *testing.T) {
90+
r := &FabricChaincodeDeployReconciler{}
91+
fc := newFabricChaincode("my-chaincode")
92+
93+
// Calling the same function multiple times must return the same result.
94+
first := r.getDeploymentName(fc)
95+
second := r.getDeploymentName(fc)
96+
if first != second {
97+
t.Errorf("getDeploymentName() not consistent: first=%q, second=%q", first, second)
98+
}
99+
100+
firstSvc := r.getServiceName(fc)
101+
secondSvc := r.getServiceName(fc)
102+
if firstSvc != secondSvc {
103+
t.Errorf("getServiceName() not consistent: first=%q, second=%q", firstSvc, secondSvc)
104+
}
105+
106+
firstSecret := r.getSecretName(fc)
107+
secondSecret := r.getSecretName(fc)
108+
if firstSecret != secondSecret {
109+
t.Errorf("getSecretName() not consistent: first=%q, second=%q", firstSecret, secondSecret)
110+
}
111+
}
112+
113+
func TestSecretNameSuffix(t *testing.T) {
114+
r := &FabricChaincodeDeployReconciler{}
115+
fc := newFabricChaincode("my-chaincode")
116+
117+
secretName := r.getSecretName(fc)
118+
if !strings.HasSuffix(secretName, "-certs") {
119+
t.Errorf("getSecretName() = %q, expected suffix '-certs'", secretName)
120+
}
121+
if !strings.HasPrefix(secretName, fc.Name) {
122+
t.Errorf("getSecretName() = %q, expected prefix %q", secretName, fc.Name)
123+
}
124+
}
125+
126+
func TestDeploymentAndServiceNameMatch(t *testing.T) {
127+
r := &FabricChaincodeDeployReconciler{}
128+
fc := newFabricChaincode("my-chaincode")
129+
130+
deploymentName := r.getDeploymentName(fc)
131+
serviceName := r.getServiceName(fc)
132+
if deploymentName != serviceName {
133+
t.Errorf("deployment name %q and service name %q should match", deploymentName, serviceName)
134+
}
135+
}

0 commit comments

Comments
 (0)