Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions ctl/authz/authz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright The Kmesh Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package authz

import (
"testing"
)

func TestNewCmd(t *testing.T) {
cmd := NewCmd()
if cmd.Use != "authz" {
t.Fatalf("Use = %q, want %q", cmd.Use, "authz")
}

got := map[string]bool{}
for _, sub := range cmd.Commands() {
got[sub.Name()] = true
}
for _, want := range []string{"enable", "disable", "status"} {
if !got[want] {
t.Errorf("subcommand %q not registered", want)
}
}
}

func TestNewEnableCmd(t *testing.T) {
cmd := NewEnableCmd()
if cmd.Use != "enable [podNames...]" {
t.Fatalf("Use = %q, want %q", cmd.Use, "enable [podNames...]")
}
}

func TestNewDisableCmd(t *testing.T) {
cmd := NewDisableCmd()
if cmd.Use != "disable [podNames...]" {
t.Fatalf("Use = %q, want %q", cmd.Use, "disable [podNames...]")
}
}

func TestNewStatusCmd(t *testing.T) {
cmd := NewStatusCmd()
if cmd.Use != "status [podNames...]" {
t.Fatalf("Use = %q, want %q", cmd.Use, "status [podNames...]")
}
}
Comment on lines +23 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

While these tests are good for verifying the command structure, they don't test the execution logic within the Run functions of the commands (e.g., for enable, disable, status). This leaves the core functionality of the authz subcommand untested.

Consider refactoring the authz package to separate the command logic from the Kubernetes client and port-forwarding setup. This would allow you to write unit tests for the logic using an httptest server, similar to how it's done in ctl/log/log_test.go. This would make the tests truly comprehensive.

For example, you could add tests that execute the command and verify that the correct HTTP request is made to the mock server.

50 changes: 50 additions & 0 deletions ctl/common/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright The Kmesh Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package common

import (
"testing"
)

func TestGetRootCommand(t *testing.T) {
rootCmd := GetRootCommand()

if rootCmd.Use != "kmeshctl" {
t.Fatalf("Use = %q, want %q", rootCmd.Use, "kmeshctl")
}
if !rootCmd.SilenceUsage {
t.Fatal("SilenceUsage should be true")
}
if !rootCmd.CompletionOptions.DisableDefaultCmd {
t.Fatal("default completion command should be disabled")
}

want := map[string]bool{
"log": false, "dump": false, "waypoint": false, "version": false,
"monitoring": false, "authz": false, "secret": false,
}
Comment on lines +36 to +39
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR title/description says unit tests were added for all kmeshctl subcommands, but this PR still has no tests for the secret subcommand (ctl/secret has no *_test.go). Either add a basic ctl/secret/secret_test.go (e.g., validates command tree/flags like other packages) or adjust the PR title/description to exclude secret from the claim.

Copilot uses AI. Check for mistakes.
for _, cmd := range rootCmd.Commands() {
if _, ok := want[cmd.Name()]; ok {
want[cmd.Name()] = true
}
}
for name, found := range want {
if !found {
t.Errorf("subcommand %q not registered", name)
}
}
}
190 changes: 190 additions & 0 deletions ctl/dump/dump_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright The Kmesh Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dump

import (
"bytes"
"encoding/json"
"io"
"os"
"strings"
"testing"

"google.golang.org/protobuf/encoding/protojson"

adminv2 "kmesh.net/kmesh/api/v2/admin"
cluster "kmesh.net/kmesh/api/v2/cluster"
)

func TestUint32ToIPStr(t *testing.T) {
tests := []struct {
ip uint32
want string
}{
{0x0100007F, "127.0.0.1"},
{0x00000000, "0.0.0.0"},
{0xFFFFFFFF, "255.255.255.255"},
{0x0101A8C0, "192.168.1.1"},
{0x0100000A, "10.0.0.1"},
}
for _, tt := range tests {
if got := uint32ToIPStr(tt.ip); got != tt.want {
t.Errorf("uint32ToIPStr(%#x) = %v, want %v", tt.ip, got, tt.want)
}
}
}

func captureStdout(t *testing.T, fn func()) string {
t.Helper()
old := os.Stdout
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("failed to create pipe: %v", err)
}
defer func() {
os.Stdout = old
_ = w.Close()
_ = r.Close()
}()

os.Stdout = w
fn()
_ = w.Close()

var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
t.Fatalf("failed to read captured stdout: %v", err)
}
return buf.String()
}

func TestPrintDualEngineTable(t *testing.T) {
tests := []struct {
name string
input workloadDump
wantEmpty bool
wantSubstr []string
}{
{
name: "full dump",
input: workloadDump{
Workloads: []workloadEntry{
{Name: "nginx", Namespace: "default", Addresses: []string{"10.0.0.1"}, Protocol: "TCP", Status: "Healthy"},
},
Services: []serviceEntry{
{Name: "my-svc", Namespace: "default", Hostname: "my-svc.default.svc.cluster.local", Addresses: []string{"10.96.0.1"}},
},
Policies: []policyEntry{
{Name: "allow-all", Namespace: "default", Scope: "namespace", Action: "ALLOW"},
},
},
wantSubstr: []string{"nginx", "my-svc", "allow-all", "ALLOW", "TCP", "Healthy"},
},
{
name: "empty dump",
input: workloadDump{},
wantEmpty: true,
},
{
name: "workloads only with multiple addresses",
input: workloadDump{
Workloads: []workloadEntry{
{Name: "app1", Namespace: "test", Addresses: []string{"10.0.0.2", "10.0.0.3"}, Protocol: "HTTP", Status: "Running"},
},
},
wantSubstr: []string{"app1", "10.0.0.2,10.0.0.3"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body, _ := json.Marshal(tt.input)
out := captureStdout(t, func() { printDualEngineTable(body) })

if tt.wantEmpty && strings.TrimSpace(out) != "" {
t.Errorf("expected empty output, got: %s", out)
}
for _, s := range tt.wantSubstr {
if !strings.Contains(out, s) {
t.Errorf("output missing %q", s)
}
}
})
}
}

func TestPrintDualEngineTable_InvalidJSON(t *testing.T) {
out := captureStdout(t, func() { printDualEngineTable([]byte("not json")) })
if !strings.Contains(out, "not json") {
t.Error("expected raw fallback output on invalid JSON")
}
}

func TestNewCmd(t *testing.T) {
cmd := NewCmd()
if cmd.Use != "dump" {
t.Fatalf("Use = %q, want %q", cmd.Use, "dump")
}
f := cmd.Flags().Lookup("output")
if f == nil {
t.Fatal("--output flag not defined")
}
if f.Shorthand != "o" {
t.Errorf("--output shorthand = %q, want %q", f.Shorthand, "o")
}
if f.DefValue != "table" {
t.Errorf("--output default = %q, want %q", f.DefValue, "table")
}
}

func TestPrintKernelNativeTable(t *testing.T) {
dump := &adminv2.ConfigDump{
StaticResources: &adminv2.ConfigResources{
ClusterConfigs: []*cluster.Cluster{
{Name: "outbound|80|default|svc.cluster.local"},
},
},
}
body, err := protojson.Marshal(dump)
if err != nil {
t.Fatalf("failed to marshal ConfigDump: %v", err)
}

out := captureStdout(t, func() { printKernelNativeTable(body) })
if !strings.Contains(out, "NAME") {
t.Error("expected NAME header in output")
}
if !strings.Contains(out, "outbound|80|default|svc.cluster.local") {
t.Error("expected cluster name in output")
}
}

func TestPrintKernelNativeTable_EmptyDump(t *testing.T) {
dump := &adminv2.ConfigDump{}
body, _ := protojson.Marshal(dump)
out := captureStdout(t, func() { printKernelNativeTable(body) })
if strings.TrimSpace(out) != "" {
t.Errorf("expected empty output for empty dump, got: %s", out)
}
}

func TestPrintKernelNativeTable_InvalidJSON(t *testing.T) {
out := captureStdout(t, func() { printKernelNativeTable([]byte("{bad")) })
if !strings.Contains(out, "{bad") {
t.Error("expected raw fallback output on invalid JSON")
}
}
Loading
Loading