package cni import ( "bytes" "net" "path/filepath" "testing" "time" current "github.com/containernetworking/cni/pkg/types/100" ) func TestEncodeDecode_RequestRoundtrip(t *testing.T) { req := Request{ Op: OpAdd, ContainerID: "abc", Netns: "/proc/1234/ns/net", IfName: "eth0", Args: "K8S_POD_NAMESPACE=mail;K8S_POD_NAME=stalwart-0", Path: "/opt/cni/bin", StdinData: []byte(`{"cniVersion":"1.0.0","name":"flock"}`), } var buf bytes.Buffer if err := EncodeRequest(&buf, req); err != nil { t.Fatal(err) } got, err := DecodeRequest(&buf) if err != nil { t.Fatal(err) } if got.Op != req.Op || got.ContainerID != req.ContainerID || string(got.StdinData) != string(req.StdinData) { t.Fatalf("roundtrip mismatch:\n got=%+v\nwant=%+v", got, req) } } func TestEncodeDecode_ResponseRoundtrip(t *testing.T) { resp := Response{ Result: ¤t.Result{CNIVersion: current.ImplementedSpecVersion}, } var buf bytes.Buffer if err := EncodeResponse(&buf, resp); err != nil { t.Fatal(err) } got, err := DecodeResponse(&buf) if err != nil { t.Fatal(err) } if got.Result == nil || got.Result.CNIVersion != current.ImplementedSpecVersion { t.Fatalf("response roundtrip lost CNIVersion: %+v", got) } } // TestRPC_ClientToFakeServer wires the real client to a tiny in-process // server over a unix socket, exercising end-to-end framing. func TestRPC_ClientToFakeServer(t *testing.T) { dir := t.TempDir() sockPath := filepath.Join(dir, "flock.sock") l, err := net.Listen("unix", sockPath) if err != nil { t.Fatal(err) } defer l.Close() // Server: read one Request, write one Response. done := make(chan error, 1) go func() { conn, err := l.Accept() if err != nil { done <- err return } defer conn.Close() req, err := DecodeRequest(conn) if err != nil { done <- err return } var resp Response switch req.Op { case OpAdd: resp.Result = ¤t.Result{CNIVersion: current.ImplementedSpecVersion} case OpDel, OpCheck: // no-op success default: resp.Error = "unknown op" } done <- EncodeResponse(conn, resp) }() // Point the client at our test socket. prev := rpcSocket rpcSocket = sockPath defer func() { rpcSocket = prev }() resp, err := call(Request{Op: OpAdd, ContainerID: "test"}) if err != nil { t.Fatalf("call: %v", err) } if resp.Result == nil { t.Fatalf("expected result, got %+v", resp) } select { case err := <-done: if err != nil { t.Fatalf("server: %v", err) } case <-time.After(2 * time.Second): t.Fatal("server did not finish") } } func TestRPC_ServerErrorPropagatesToCNIError(t *testing.T) { resp := &Response{Error: "no NodeConfig for host001"} err := toCNIError("add", resp) if err == nil { t.Fatal("expected CNI error") } if got := err.Error(); got == "" || got == "no NodeConfig for host001" { // types.Error wraps the message — just make sure something non-empty // surfaces and that the underlying string is contained. t.Fatalf("unexpected error format: %q", got) } }