@@ -15,6 +15,7 @@ import (
1515
1616 "github.com/containernetworking/cni/pkg/skel"
1717 "github.com/containernetworking/cni/pkg/types"
18+ type100 "github.com/containernetworking/cni/pkg/types/100"
1819 bgpv1alpha1 "go.miloapis.com/cosmos/api/bgp/v1alpha1"
1920 apierrors "k8s.io/apimachinery/pkg/api/errors"
2021 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -30,6 +31,15 @@ const (
3031 testRD65000_1 = "65000:1" // RD/RT for ASN 65000, NN 1
3132 testContainerID = "test-container"
3233 testInvalidBase62 = "abc-def" // shared invalid base62 string for tests
34+ testNetns = "/proc/1/ns/net"
35+ testMac = "aa:bb:cc:dd:ee:ff"
36+ testIfName = "eth0"
37+
38+ // testPrevResult is a valid CNI v1.0.0 result used in prevResult tests.
39+ testPrevResult = `{"cniVersion":"1.0.0",` +
40+ `"interfaces":[{"name":"` + testIfName + `","mac":"` + testMac + `",` +
41+ `"sandbox":"/proc/1/ns/net"}],` +
42+ `"ips":[{"version":"6","address":"fd00:1::1/64"}]}`
3343)
3444
3545func fakeClient (objs ... client.Object ) client.Client {
@@ -292,6 +302,18 @@ func TestParseConf(t *testing.T) {
292302 ),
293303 wantErr : srv6LocatorErrMsg ,
294304 },
305+ {
306+ name : "prevResult valid JSON result is accepted" ,
307+ input : fmt .Sprintf (
308+ `{"cniVersion":"1.0.0","name":"test",` +
309+ `"type":"galactic-cni","vpc":"%s",` +
310+ `"vpcattachment":"%s",` +
311+ `"prevResult":%s}` ,
312+ testVPC , testAttachment , testPrevResult ,
313+ ),
314+ wantVPC : testVPC ,
315+ wantIfType : interfaceTypeVeth ,
316+ },
295317 }
296318
297319 for _ , tt := range tests {
@@ -414,6 +436,92 @@ func TestSanitizeForError(t *testing.T) {
414436 }
415437}
416438
439+ // ---- validatePrevResult --------------------------------------------------
440+
441+ func TestValidatePrevResult (t * testing.T ) {
442+ validResult := & type100.Result {
443+ CNIVersion : cniVersion100 ,
444+ Interfaces : []* type100.Interface {
445+ {Name : testIfName , Mac : testMac , Sandbox : testNetns },
446+ },
447+ IPs : []* type100.IPConfig {
448+ {Address : * mustParseCIDR (t , "fd00:1::1/64" )},
449+ },
450+ }
451+
452+ tests := []struct {
453+ name string
454+ input types.Result
455+ wantErr bool
456+ }{
457+ {"nil result allowed" , nil , false },
458+ {"valid CNI result" , validResult , false },
459+ }
460+
461+ for _ , tt := range tests {
462+ t .Run (tt .name , func (t * testing.T ) {
463+ err := validatePrevResult (tt .input )
464+ if tt .wantErr {
465+ if err == nil {
466+ t .Fatal ("expected error, got nil" )
467+ }
468+ return
469+ }
470+ if err != nil {
471+ t .Fatalf ("unexpected error: %v" , err )
472+ }
473+ })
474+ }
475+ }
476+
477+ func TestValidatePrevResultAdd (t * testing.T ) {
478+ validWithInterface := & type100.Result {
479+ CNIVersion : cniVersion100 ,
480+ Interfaces : []* type100.Interface {
481+ {Name : testIfName , Mac : testMac , Sandbox : testNetns },
482+ },
483+ IPs : []* type100.IPConfig {
484+ {Address : * mustParseCIDR (t , "fd00:1::1/64" )},
485+ },
486+ }
487+ validWithIPsOnly := & type100.Result {
488+ CNIVersion : cniVersion100 ,
489+ IPs : []* type100.IPConfig {
490+ {Address : * mustParseCIDR (t , "fd00:1::1/64" )},
491+ },
492+ }
493+ emptyResult := & type100.Result {
494+ CNIVersion : cniVersion100 ,
495+ // No interfaces, no IPs — should fail content validation.
496+ }
497+
498+ tests := []struct {
499+ name string
500+ input types.Result
501+ wantErr bool
502+ }{
503+ {"nil result allowed" , nil , false },
504+ {"valid result with interface" , validWithInterface , false },
505+ {"valid result with IPs only" , validWithIPsOnly , false },
506+ {"empty result (no interfaces or IPs)" , emptyResult , true },
507+ }
508+
509+ for _ , tt := range tests {
510+ t .Run (tt .name , func (t * testing.T ) {
511+ err := validatePrevResultAdd (tt .input )
512+ if tt .wantErr {
513+ if err == nil {
514+ t .Fatal ("expected error, got nil" )
515+ }
516+ return
517+ }
518+ if err != nil {
519+ t .Fatalf ("unexpected error: %v" , err )
520+ }
521+ })
522+ }
523+ }
524+
417525// ---- bgpVRFInstanceName --------------------------------------------------
418526
419527func TestBGPVRFInstanceName (t * testing.T ) {
@@ -1336,3 +1444,33 @@ func TestProbeAPIServerMalformedKubeconfig(t *testing.T) {
13361444 t .Fatalf ("error %q does not contain original error" , err .Error ())
13371445 }
13381446}
1447+
1448+ // ---- cmdAdd prevResult validation ----------------------------------------
1449+
1450+ func TestCmdAddPrevResultValid (t * testing.T ) {
1451+ // prevResult that is a valid CNI result. cmdAdd should pass prevResult
1452+ // validation and fail later due to missing NODE_NAME env var.
1453+ conf := fmt .Sprintf (
1454+ `{"cniVersion":"1.0.0","name":"test",` +
1455+ `"type":"galactic-cni","vpc":"%s",` +
1456+ `"vpcattachment":"%s",` +
1457+ `"prevResult":%s}` ,
1458+ testVPC , testAttachment , testPrevResult ,
1459+ )
1460+ args := & skel.CmdArgs {
1461+ ContainerID : testContainerID ,
1462+ StdinData : []byte (conf ),
1463+ }
1464+
1465+ err := cmdAdd (args )
1466+ if err == nil {
1467+ t .Fatal ("expected cmdAdd to fail for missing NODE_NAME, got nil" )
1468+ }
1469+ // Should fail on NODE_NAME, not prevResult.
1470+ if strings .Contains (err .Error (), "prevResult validation in ADD" ) {
1471+ t .Fatalf ("prevResult should not cause error when valid, got: %v" , err )
1472+ }
1473+ if ! strings .Contains (err .Error (), "NODE_NAME" ) {
1474+ t .Fatalf ("expected NODE_NAME error, got: %v" , err )
1475+ }
1476+ }
0 commit comments