From dc6d85bc34f87fd366d096da4e5ef0ea167f77fd Mon Sep 17 00:00:00 2001 From: Matthias Bertschy Date: Tue, 14 Nov 2023 09:55:53 +0100 Subject: [PATCH 1/7] bump for security fixes Signed-off-by: Matthias Bertschy --- go.mod | 8 ++++---- go.sum | 10 ++++------ httphandler/go.mod | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index abe28121..2fb74726 100644 --- a/go.mod +++ b/go.mod @@ -389,7 +389,7 @@ require ( go.mongodb.org/mongo-driver v1.11.3 // indirect go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.41.0 // indirect @@ -404,8 +404,8 @@ require ( go.step.sm/crypto v0.32.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/net v0.15.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect @@ -419,7 +419,7 @@ require ( google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect - google.golang.org/grpc v1.58.1 // indirect + google.golang.org/grpc v1.58.3 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 7a07637b..123e4b53 100644 --- a/go.sum +++ b/go.sum @@ -1288,8 +1288,6 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548 h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4= -github.com/johnfercher/maroto v0.42.0 h1:NlZQsSyfDnBcGBZ6M6ZV+PDrdwzoiClzbWp872viP+g= -github.com/johnfercher/maroto v0.42.0/go.mod h1:qeujdhKT+677jMjGWlIa5OCgR04GgIHvByJ6pSC+hOw= github.com/johnfercher/maroto v1.0.0 h1:yo26a/Mxj2YbHCzpIW7FypKtdvv9BdeLNHaApHwLCXU= github.com/johnfercher/maroto v1.0.0/go.mod h1:qeujdhKT+677jMjGWlIa5OCgR04GgIHvByJ6pSC+hOw= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -1984,8 +1982,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2111,8 +2109,8 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/httphandler/go.mod b/httphandler/go.mod index 794e2753..de66c727 100644 --- a/httphandler/go.mod +++ b/httphandler/go.mod @@ -37,7 +37,7 @@ require ( golang.org/x/oauth2 v0.12.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.58.1 // indirect + google.golang.org/grpc v1.58.3 // indirect ) require ( @@ -399,7 +399,7 @@ require ( go.mongodb.org/mongo-driver v1.11.4 // indirect go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.41.0 // indirect From a2f632beb492b88f555d0a2d17fb597c88c1a6a8 Mon Sep 17 00:00:00 2001 From: Mehdi Date: Mon, 20 Nov 2023 23:01:38 +0100 Subject: [PATCH 2/7] Test suite for core/pkg/score Signed-off-by: Mehdi Moussaif Use cautils for mock data Signed-off-by: Mehdi Moussaif Removed test TestNewScoreWrapperWithNilOPASessionObj Signed-off-by: Mehdi Moussaif --- core/pkg/score/score_test.go | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/core/pkg/score/score_test.go b/core/pkg/score/score_test.go index 323f8eed..e75c5d9c 100644 --- a/core/pkg/score/score_test.go +++ b/core/pkg/score/score_test.go @@ -1 +1,60 @@ package score + +import ( + "testing" + + cautils "github.com/kubescape/kubescape/v3/core/cautils" + "github.com/stretchr/testify/assert" +) + +func TestNewScoreWrapper(t *testing.T) { + opaSessionObj := cautils.NewOPASessionObjMock() + + scoreWrapper := NewScoreWrapper(opaSessionObj) + + assert.NotNil(t, scoreWrapper) + assert.NotNil(t, scoreWrapper.scoreUtil) + assert.Equal(t, opaSessionObj, scoreWrapper.opaSessionObj) +} + +func TestNewScoreWrapperWithNilAllResources(t *testing.T) { + opaSessionObj := &cautils.OPASessionObj{ + AllResources: nil, + } + scoreWrapper := NewScoreWrapper(opaSessionObj) + + assert.NotNil(t, scoreWrapper) + assert.NotNil(t, scoreWrapper.scoreUtil) + assert.NotNil(t, scoreWrapper.opaSessionObj) + assert.Nil(t, scoreWrapper.opaSessionObj.AllResources) + assert.Empty(t, scoreWrapper.opaSessionObj.AllResources) +} + +func TestCalculateReturnsNilErrorWhenReportVersionIsEPostureReportV2(t *testing.T) { + opaSessionObj := cautils.NewOPASessionObjMock() + scoreWrapper := NewScoreWrapper(opaSessionObj) + + err := scoreWrapper.Calculate(EPostureReportV2) + + assert.Nil(t, err) +} + +func TestCalculateReturnsErrorWhenReportVersionIsEPostureReportV1(t *testing.T) { + opaSessionObj := &cautils.OPASessionObj{} + scoreWrapper := NewScoreWrapper(opaSessionObj) + + err := scoreWrapper.Calculate(EPostureReportV1) + + assert.Error(t, err) + assert.Equal(t, "unsupported score calculator", err.Error()) +} + +func TestCalculateReturnsErrorWhenReportVersionIsNotSupported(t *testing.T) { + opaSessionObj := &cautils.OPASessionObj{} + scoreWrapper := NewScoreWrapper(opaSessionObj) + + err := scoreWrapper.Calculate("v3") + + assert.Error(t, err) + assert.Equal(t, "unsupported score calculator", err.Error()) +} From c7f9a6ebc4e9b63c82c649a41e0126863a1d55e9 Mon Sep 17 00:00:00 2001 From: Mehdi Moussaif Date: Tue, 21 Nov 2023 19:54:25 +0100 Subject: [PATCH 3/7] Adding Test Suite for core/pkg/resultshandling/gotree Signed-off-by: Mehdi Moussaif Additional tests for core/pkg/gotree Signed-off-by: Mehdi Moussaif tests for printItem in core/pkg/gotree Signed-off-by: Mehdi Moussaif Refactor printer initialization in gotree_test.go Signed-off-by: Mehdi Moussaif --- .../pkg/resultshandling/gotree/gotree_mock.go | 57 ++++++ .../pkg/resultshandling/gotree/gotree_test.go | 171 ++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 core/pkg/resultshandling/gotree/gotree_mock.go create mode 100644 core/pkg/resultshandling/gotree/gotree_test.go diff --git a/core/pkg/resultshandling/gotree/gotree_mock.go b/core/pkg/resultshandling/gotree/gotree_mock.go new file mode 100644 index 00000000..c2e20784 --- /dev/null +++ b/core/pkg/resultshandling/gotree/gotree_mock.go @@ -0,0 +1,57 @@ +package gotree + +func EmptyTreeMock() Tree { + tree := New("") + + return tree +} + +func RootTreeMock() Tree { + tree := New("root") + + return tree +} + +func SimpleTreeMock() Tree { + tree := New("root") + tree.Add("child1") + tree.Add("child2") + + return tree +} + +func SimpleTreeWithLinesMock() Tree { + tree := New("root") + tree.Add("child1") + tree.Add("child2") + + tree.Add("child3\nLine2\nLine3") + tree.Add("child4") + + return tree +} + +func SubTreeMock1() Tree { + tree := New("root") + tree.Add("child1").Add("child1.1") + + return tree +} + +func SubTreeMock2() Tree { + tree := New("root") + tree.Add("child1").Add("child1.1") + tree.Add("child2") + tree.Add("child3").Add("child3.1") + + return tree +} + +func SubTreeWithLinesMock() Tree { + tree := New("root") + tree.Add("child1").Add("child1.1\nLine2\nLine3") + tree.Add("child2") + tree.Add("child3").Add("child3.1\nLine2\nLine3") + + return tree +} diff --git a/core/pkg/resultshandling/gotree/gotree_test.go b/core/pkg/resultshandling/gotree/gotree_test.go new file mode 100644 index 00000000..1ec02e65 --- /dev/null +++ b/core/pkg/resultshandling/gotree/gotree_test.go @@ -0,0 +1,171 @@ +package gotree + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + p = &printer{} +) + +func TestTreePrint(t *testing.T) { + tests := []struct { + name string + tree Tree + want string + }{ + { + name: "EmptyTreeMock", + tree: EmptyTreeMock(), + want: "\n", + }, + { + name: "RootTreeMock", + tree: RootTreeMock(), + want: "root\n", + }, + { + name: "SimpleTreeMock", + tree: SimpleTreeMock(), + want: "root\n" + + "├── child1\n" + + "└── child2\n", + }, + { + name: "SimpleTreeWithLinesMock", + tree: SimpleTreeWithLinesMock(), + want: "root\n" + + "├── child1\n" + + "├── child2\n" + + "├── child3\n" + + "│ Line2\n" + + "│ Line3\n" + + "└── child4\n", + }, + { + name: "SubTreeMock1", + tree: SubTreeMock1(), + want: "root\n" + + "└── child1\n" + + " └── child1.1\n", + }, + { + name: "SubTreeMock2", + tree: SubTreeMock2(), + want: "root\n" + + "├── child1\n" + + "│ └── child1.1\n" + + "├── child2\n" + + "└── child3\n" + + " └── child3.1\n", + }, + { + name: "SubTreeWithLinesMock", + tree: SubTreeWithLinesMock(), + want: "root\n" + + "├── child1\n" + + "│ └── child1.1\n" + + "│ Line2\n" + + "│ Line3\n" + + "├── child2\n" + + "└── child3\n" + + " └── child3.1\n" + + " Line2\n" + + " Line3\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.tree.Print()) + }) + } +} + +func TestPrintText_LastTree(t *testing.T) { + inputText := "Root\n├── Child1\n└── Child2" + expectedOutput := "└── Root\n ├── Child1\n └── Child2\n" + + result := p.printText(inputText, []bool{}, true) + + assert.Equal(t, expectedOutput, result) +} + +func TestPrintText_NotLastTree(t *testing.T) { + inputText := "Root\n├── Child1\n└── Child2" + expectedOutput := "├── Root\n│ ├── Child1\n│ └── Child2\n" + + result := p.printText(inputText, []bool{}, false) + + assert.Equal(t, expectedOutput, result) +} + +func Test_printer_printItems(t *testing.T) { + tests := []struct { + name string + tree Tree + want string + }{ + { + name: "EmptyTreeMock", + tree: EmptyTreeMock(), + want: "", + }, + { + name: "RootTreeMock", + tree: RootTreeMock(), + want: "", + }, + { + name: "SimpleTreeMock", + tree: SimpleTreeMock(), + want: "├── child1\n" + + "└── child2\n", + }, + { + name: "SimpleTreeWithLinesMock", + tree: SimpleTreeWithLinesMock(), + want: "├── child1\n" + + "├── child2\n" + + "├── child3\n" + + "│ Line2\n" + + "│ Line3\n" + + "└── child4\n", + }, + { + name: "SubTreeMock1", + tree: SubTreeMock1(), + want: "└── child1\n" + + " └── child1.1\n", + }, + { + name: "SubTreeMock2", + tree: SubTreeMock2(), + want: "├── child1\n" + + "│ └── child1.1\n" + + "├── child2\n" + + "└── child3\n" + + " └── child3.1\n", + }, + { + name: "SubTreeWithLinesMock", + tree: SubTreeWithLinesMock(), + want: "├── child1\n" + + "│ └── child1.1\n" + + "│ Line2\n" + + "│ Line3\n" + + "├── child2\n" + + "└── child3\n" + + " └── child3.1\n" + + " Line2\n" + + " Line3\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, p.printItems(tt.tree.Items(), []bool{})) + }) + } +} From 16f48493235d12c43c40e6c0a99b87c751ac4ffe Mon Sep 17 00:00:00 2001 From: VaibhavMalik4187 Date: Wed, 22 Nov 2023 15:10:28 +0530 Subject: [PATCH 4/7] Run binary-build job only for kubescape/kubscape Added a check before running the binary-build job to ensure that the owner of the repository is kubescape. Fixes: https://github.com/kubescape/kubescape/issues/1482 Signed-off-by: VaibhavMalik4187 --- .github/workflows/00-pr-scanner.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/00-pr-scanner.yaml b/.github/workflows/00-pr-scanner.yaml index cfe4a8b4..82368e87 100644 --- a/.github/workflows/00-pr-scanner.yaml +++ b/.github/workflows/00-pr-scanner.yaml @@ -20,6 +20,7 @@ concurrency: jobs: pr-scanner: + if: github.repository_owner == 'kubescape' permissions: actions: read checks: read From 9845175d29bef8c72f1f355c5262d1f46a2928f8 Mon Sep 17 00:00:00 2001 From: VaibhavMalik4187 Date: Tue, 21 Nov 2023 20:15:56 +0530 Subject: [PATCH 5/7] Added tests for the core/core package Wrote tests for the following files: - kscore.go - list.go - patch.go - scan.go Signed-off-by: VaibhavMalik4187 --- core/core/kscore_test.go | 23 ++++ core/core/list_test.go | 255 +++++++++++++++++++++++++++++++++++++++ core/core/patch_test.go | 15 +++ core/core/scan_test.go | 46 +++++++ 4 files changed, 339 insertions(+) create mode 100644 core/core/kscore_test.go create mode 100644 core/core/list_test.go create mode 100644 core/core/patch_test.go create mode 100644 core/core/scan_test.go diff --git a/core/core/kscore_test.go b/core/core/kscore_test.go new file mode 100644 index 00000000..22b7f6f7 --- /dev/null +++ b/core/core/kscore_test.go @@ -0,0 +1,23 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// The function should return a non-nil pointer. +func TestNewKubescape_ReturnsNonNilPointer(t *testing.T) { + k := NewKubescape() + assert.NotNil(t, k) +} + +// The function should not panic. +func TestNewKubescape_DoesNotPanic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Function panicked: %v", r) + } + }() + NewKubescape() +} diff --git a/core/core/list_test.go b/core/core/list_test.go new file mode 100644 index 00000000..d309eb63 --- /dev/null +++ b/core/core/list_test.go @@ -0,0 +1,255 @@ +package core + +import ( + "context" + "encoding/json" + "io" + "os" + "sort" + "testing" + + "github.com/stretchr/testify/assert" +) + +// Function receives a non-empty list of policies +func TestNonEmptyListOfPolicies(t *testing.T) { + policies := []string{"policy1", "policy2", "policy3"} + + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + jsonListFormat(context.Background(), "", policies) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + // got := buf.String() + want := `[ + "policy1", + "policy2", + "policy3" +] +` + assert.Equal(t, want, string(got)) +} + +// Function returns a valid JSON string +func TestValidJsonString(t *testing.T) { + policies := []string{"policy1", "policy2", "policy3"} + + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + jsonListFormat(context.Background(), "", policies) + + w.Close() + out, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + var result []string + err := json.Unmarshal(out, &result) + assert.NoError(t, err) +} + +// Function receives an empty list of policies +func TestEmptyListOfPolicies(t *testing.T) { + policies := []string{} + + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + jsonListFormat(context.Background(), "", policies) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + want := "[]\n" + + assert.Equal(t, want, string(got)) +} + +// Function receives a nil list of policies +func TestNilListOfPolicies(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + jsonListFormat(context.Background(), "", nil) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + want := "null\n" + + assert.Equal(t, want, string(got)) +} + +// Returns a 2D slice with one row for each policy in the input slice. +func TestGeneratePolicyRows_NonEmptyPolicyList(t *testing.T) { + // Arrange + policies := []string{"policy1", "policy2", "policy3"} + + // Act + result := generatePolicyRows(policies) + + // Assert + assert.Equal(t, [][]string{{"policy1"}, {"policy2"}, {"policy3"}}, result) +} + +// Returns an empty 2D slice for an empty list of policies. +func TestGeneratePolicyRows_EmptyPolicyList(t *testing.T) { + // Arrange + policies := []string{} + + // Act + got := generatePolicyRows(policies) + + // Assert + assert.Empty(t, got) +} + +// The function returns a list of rows, each containing a formatted string with control ID, control name, docs, and frameworks. +func TestShortFormatControlRows_ReturnsListOfRowsWithFormattedString(t *testing.T) { + controlRows := [][]string{ + {"ID1", "Control 1", "Docs 1", "Framework 1"}, + {"ID2", "Control 2", "Docs 2", "Framework 2"}, + } + + want := [][]string{ + {"Control ID : ID1\nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"}, + {"Control ID : ID2\nControl Name : Control 2\nDocs : Docs 2\nFrameworks : Framework 2"}, + } + + got := shortFormatControlRows(controlRows) + + assert.Equal(t, want, got) +} + +// The function formats the control rows correctly, replacing newlines in the frameworks column with line breaks. +func TestShortFormatControlRows_FormatsControlRowsCorrectly(t *testing.T) { + controlRows := [][]string{ + {"ID1", "Control 1", "Docs 1", "Framework\n1"}, + {"ID2", "Control 2", "Docs 2", "Framework\n2"}, + } + + want := [][]string{ + {"Control ID : ID1\nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"}, + {"Control ID : ID2\nControl Name : Control 2\nDocs : Docs 2\nFrameworks : Framework 2"}, + } + + result := shortFormatControlRows(controlRows) + + assert.Equal(t, want, result) +} + +// The function handles a control row with an empty control ID. +func TestShortFormatControlRows_HandlesControlRowWithEmptyControlID(t *testing.T) { + controlRows := [][]string{ + {"", "Control 1", "Docs 1", "Framework 1"}, + } + + want := [][]string{ + {"Control ID : \nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"}, + } + + got := shortFormatControlRows(controlRows) + + assert.Equal(t, want, got) +} + +// The function handles a control row with an empty control name. +func TestShortFormatControlRows_HandlesControlRowWithEmptyControlName(t *testing.T) { + controlRows := [][]string{ + {"ID1", "", "Docs 1", "Framework 1"}, + } + + want := [][]string{ + {"Control ID : ID1\nControl Name : \nDocs : Docs 1\nFrameworks : Framework 1"}, + } + + got := shortFormatControlRows(controlRows) + + assert.Equal(t, want, got) +} + +// Generates rows for each policy with ID, control, documentation, and framework +func TestGenerateControlRowsWithAllFields(t *testing.T) { + policies := []string{ + "1|Control 1|Framework 1", + "2|Control 2|Framework 2", + "3|Control 3|Framework 3", + } + + want := [][]string{ + {"1", "Control 1", "https://hub.armosec.io/docs/1", "Framework\n1"}, + {"2", "Control 2", "https://hub.armosec.io/docs/2", "Framework\n2"}, + {"3", "Control 3", "https://hub.armosec.io/docs/3", "Framework\n3"}, + } + + got := generateControlRows(policies) + + assert.Equal(t, want, got) +} + +// The function generates a table with the correct headers and rows based on the input policies. +func TestGenerateTableWithCorrectHeadersAndRows(t *testing.T) { + // Arrange + ctx := context.Background() + policies := []string{ + "1|Control 1|Framework 1", + "2|Control 2|Framework 2", + "3|Control 3|Framework 3", + } + + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + prettyPrintControls(ctx, policies) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + // got := buf.String() + want := `┌────────────┬──────────────┬───────────────────────────────┬────────────┐ +│ Control ID │ Control name │ Docs │ Frameworks │ +├────────────┼──────────────┼───────────────────────────────┼────────────┤ +│ 1 │ Control 1 │ https://hub.armosec.io/docs/1 │ Framework │ +│ │ │ │ 1 │ +├────────────┼──────────────┼───────────────────────────────┼────────────┤ +│ 2 │ Control 2 │ https://hub.armosec.io/docs/2 │ Framework │ +│ │ │ │ 2 │ +├────────────┼──────────────┼───────────────────────────────┼────────────┤ +│ 3 │ Control 3 │ https://hub.armosec.io/docs/3 │ Framework │ +│ │ │ │ 3 │ +└────────────┴──────────────┴───────────────────────────────┴────────────┘ +` + + assert.Equal(t, want, string(got)) +} + +// Returns a non-empty list of supported actions when 'ListSupportActions' is called. +func TestListSupportActionsNotEmpty(t *testing.T) { + actions := ListSupportActions() + assert.NotEmpty(t, actions) +} + +func TestListSupportActionsReturnsSupportedActions(t *testing.T) { + got := ListSupportActions() + want := []string{"controls", "exceptions", "frameworks"} + sort.Strings(got) + + assert.Equal(t, want, got) +} diff --git a/core/core/patch_test.go b/core/core/patch_test.go new file mode 100644 index 00000000..67ba7cc7 --- /dev/null +++ b/core/core/patch_test.go @@ -0,0 +1,15 @@ +package core + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +// sets os.Stdout and os.Stderr to nil +func TestSetsOsStdoutAndStderrToNil(t *testing.T) { + disableCopaLogger() + assert.Nil(t, os.Stdout) + assert.Nil(t, os.Stderr) +} diff --git a/core/core/scan_test.go b/core/core/scan_test.go new file mode 100644 index 00000000..920c9e2a --- /dev/null +++ b/core/core/scan_test.go @@ -0,0 +1,46 @@ +package core + +import ( + "testing" + + "github.com/kubescape/kubescape/v3/core/cautils" + "github.com/stretchr/testify/assert" +) + +func TestIsPrioritizationScanType(t *testing.T) { + tests := []struct { + name cautils.ScanTypes + want bool + }{ + { + name: cautils.ScanTypeCluster, + want: true, + }, + { + name: cautils.ScanTypeRepo, + want: true, + }, + { + name: cautils.ScanTypeImage, + want: false, + }, + { + name: cautils.ScanTypeWorkload, + want: false, + }, + { + name: cautils.ScanTypeFramework, + want: false, + }, + { + name: cautils.ScanTypeControl, + want: false, + }, + } + + for _, tt := range tests { + t.Run(string(tt.name), func(t *testing.T) { + assert.Equal(t, tt.want, isPrioritizationScanType(tt.name)) + }) + } +} From beb6d9535cdcf968442d2a346fd962a6297b7727 Mon Sep 17 00:00:00 2001 From: VaibhavMalik4187 Date: Tue, 21 Nov 2023 19:49:33 +0530 Subject: [PATCH 6/7] Added tests for cautils Wrote new tests for the following files: - controllink.go - display.go - fileutils.go - getter/getpoliciesutils.go - getter/utils.go Signed-off-by: VaibhavMalik4187 --- core/cautils/controllink_test.go | 53 +++ core/cautils/display_test.go | 422 +++++++++++++++++++ core/cautils/fileutils_test.go | 107 +++++ core/cautils/getter/getpoliciesutils_test.go | 45 ++ core/cautils/getter/utils_test.go | 43 ++ 5 files changed, 670 insertions(+) create mode 100644 core/cautils/controllink_test.go create mode 100644 core/cautils/getter/utils_test.go diff --git a/core/cautils/controllink_test.go b/core/cautils/controllink_test.go new file mode 100644 index 00000000..441d39ed --- /dev/null +++ b/core/cautils/controllink_test.go @@ -0,0 +1,53 @@ +package cautils + +import ( + "testing" +) + +// Returns a valid URL when given a valid control ID. +func TestGetControlLink_ValidControlID(t *testing.T) { + controlID := "cis-1.1.3" + expectedURL := "https://hub.armosec.io/docs/cis-1-1-3" + + result := GetControlLink(controlID) + + if result != expectedURL { + t.Errorf("Expected URL: %s, but got: %s", expectedURL, result) + } +} + +// Replaces dots with hyphens in the control ID to generate the correct documentation link. +func TestGetControlLink_DotsInControlID(t *testing.T) { + controlID := "cis.1.1.3" + expectedURL := "https://hub.armosec.io/docs/cis-1-1-3" + + result := GetControlLink(controlID) + + if result != expectedURL { + t.Errorf("Expected URL: %s, but got: %s", expectedURL, result) + } +} + +// Returns a lowercase URL. +func TestGetControlLink_LowercaseURL(t *testing.T) { + controlID := "CIS-1.1.3" + expectedURL := "https://hub.armosec.io/docs/cis-1-1-3" + + result := GetControlLink(controlID) + + if result != expectedURL { + t.Errorf("Expected URL: %s, but got: %s", expectedURL, result) + } +} + +// Returns URL to armosec docs when given an empty control ID. +func TestGetControlLink_EmptyControlID(t *testing.T) { + controlID := "" + expectedURL := "https://hub.armosec.io/docs/" + + result := GetControlLink(controlID) + + if result != expectedURL { + t.Errorf("Expected URL: %s, but got: %s", expectedURL, result) + } +} diff --git a/core/cautils/display_test.go b/core/cautils/display_test.go index 85bb7533..718240dd 100644 --- a/core/cautils/display_test.go +++ b/core/cautils/display_test.go @@ -1,9 +1,12 @@ package cautils import ( + "io" + "os" "testing" "github.com/kubescape/go-logger" + "github.com/stretchr/testify/assert" ) func TestStartSpinner(t *testing.T) { @@ -30,3 +33,422 @@ func TestStartSpinner(t *testing.T) { }) } } + +func TestFailureDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + FailureDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestWarningDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + WarningDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestFailureTextDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + FailureTextDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestInfoDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + InfoDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestInfoTextDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + InfoTextDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestSimpleDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + SimpleDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestSuccessDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + SuccessDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestDescriptionDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + DescriptionDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestBoldDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + BoldDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestLineDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "Test", + }, + { + text: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + LineDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestSectionHeadingDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test Section", + want: "\nTest Section\n────────────\n\n", + }, + { + text: "", + want: "\n\n\n\n", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + SectionHeadingDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +func TestStarDisplay(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "Test", + want: "* Test", + }, + { + text: "", + want: "* ", + }, + } + + for _, tt := range tests { + t.Run(tt.text, func(t *testing.T) { + // Redirect stdout to a buffer + rescueStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + StarDisplay(os.Stdout, tt.text) + + w.Close() + got, _ := io.ReadAll(r) + os.Stdout = rescueStdout + + assert.Equal(t, tt.want, string(got)) + }) + } +} + +// Returns a new instance of ProgressHandler with the given title. +func TestNewProgressHandler_(t *testing.T) { + tests := []struct { + title string + }{ + { + title: "Test title", + }, + { + title: "", + }, + } + + for _, tt := range tests { + t.Run(tt.title, func(t *testing.T) { + progressHandler := NewProgressHandler(tt.title) + assert.NotNil(t, progressHandler) + + assert.Equal(t, tt.title, progressHandler.title) + }) + } +} diff --git a/core/cautils/fileutils_test.go b/core/cautils/fileutils_test.go index 28fa94bb..b132f75a 100644 --- a/core/cautils/fileutils_test.go +++ b/core/cautils/fileutils_test.go @@ -105,3 +105,110 @@ func getRelativePath(p string) string { pp := strings.SplitAfter(p, "api=") return pp[1] } + +// Converts a YAML object to a JSON object +func TestConvertYamlToJson(t *testing.T) { + tests := []struct { + yamlObj map[interface{}]interface{} + jsonObj map[string]interface{} + }{ + { + yamlObj: map[interface{}]interface{}{ + "name": "John", + "age": 30, + "city": "New York", + }, + jsonObj: map[string]interface{}{ + "name": "John", + "age": 30, + "city": "New York", + }, + }, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + assert.Equal(t, tt.jsonObj, convertYamlToJson(tt.yamlObj)) + }) + } +} + +func TestIsYaml(t *testing.T) { + tests := []struct { + path string + want bool + }{ + { + path: "temp.yaml", + want: true, + }, + { + path: "temp.json", + want: false, + }, + { + path: "random.txt", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + assert.Equal(t, tt.want, IsYaml(tt.path)) + }) + } +} + +func TestIsJson(t *testing.T) { + tests := []struct { + path string + want bool + }{ + { + path: "temp.yaml", + want: false, + }, + { + path: "temp.json", + want: true, + }, + { + path: "random.txt", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + assert.Equal(t, tt.want, IsJson(tt.path)) + }) + } + +} + +func TestGetFileFormat(t *testing.T) { + tests := []struct { + path string + want FileFormat + }{ + { + path: "temp.yaml", + want: YAML_FILE_FORMAT, + }, + { + path: "temp.json", + want: JSON_FILE_FORMAT, + }, + { + path: "random.txt", + want: "random.txt", + }, + } + + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + assert.Equal(t, tt.want, GetFileFormat(tt.path)) + }) + } + +} diff --git a/core/cautils/getter/getpoliciesutils_test.go b/core/cautils/getter/getpoliciesutils_test.go index 3f56bd70..884a774b 100644 --- a/core/cautils/getter/getpoliciesutils_test.go +++ b/core/cautils/getter/getpoliciesutils_test.go @@ -1,12 +1,15 @@ package getter import ( + "io/ioutil" "net/http" "os" "path/filepath" + "strings" "testing" beClient "github.com/kubescape/backend/pkg/client/v1" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -96,3 +99,45 @@ func TestHttpMethods(t *testing.T) { require.EqualValues(t, "body-delete", resp) }) } + +// Returns an empty string and nil error when given a nil response or nil response body. +func TestHttpRespToString_NilResponse(t *testing.T) { + resp := &http.Response{} + result, err := httpRespToString(resp) + assert.Equal(t, "", result) + assert.Nil(t, err) +} + +func TestHttpRespToString_ValidResponse(t *testing.T) { + resp := &http.Response{ + Body: ioutil.NopCloser(strings.NewReader("test response")), + Status: "200 OK", + StatusCode: 200, + } + result, err := httpRespToString(resp) + assert.Equal(t, "test response", result) + assert.Nil(t, err) +} + +// Returns an error with status and reason when unable to read response body. +func TestHttpRespToString_ReadError(t *testing.T) { + resp := &http.Response{ + Body: ioutil.NopCloser(strings.NewReader("test response")), + } + resp.Body.Close() + result, err := httpRespToString(resp) + assert.EqualError(t, err, "http-error: '', reason: 'test response'") + assert.Equal(t, "test response", result) +} + +// Returns an error with status and reason when unable to read response body. +func TestHttpRespToString_ErrorCodeLessThan200(t *testing.T) { + resp := &http.Response{ + Body: ioutil.NopCloser(strings.NewReader("test response")), + StatusCode: 100, + } + resp.Body.Close() + result, err := httpRespToString(resp) + assert.EqualError(t, err, "http-error: '', reason: 'test response'") + assert.Equal(t, "test response", result) +} diff --git a/core/cautils/getter/utils_test.go b/core/cautils/getter/utils_test.go new file mode 100644 index 00000000..a5937ff2 --- /dev/null +++ b/core/cautils/getter/utils_test.go @@ -0,0 +1,43 @@ +package getter + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// should return true if the string is present in the slice +func TestContains(t *testing.T) { + tests := []struct { + str []string + key string + want bool + }{ + { + str: []string{"apple", "banana", "orange"}, + key: "banana", + want: true, + }, + { + str: []string{"apple", "banana", "orange"}, + key: "mango", + want: false, + }, + { + str: []string{"", "banana", "banana"}, + key: "banana", + want: true, + }, + { + str: []string{"", "", ""}, + key: "grape", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.key, func(t *testing.T) { + assert.Equal(t, tt.want, contains(tt.str, tt.key)) + }) + } +} From 55da8c1ce2dc55c454d7b4c069465f0683681556 Mon Sep 17 00:00:00 2001 From: VaibhavMalik4187 Date: Tue, 21 Nov 2023 14:41:38 +0530 Subject: [PATCH 7/7] Added signal handler for the interrupt signal - Subscribing to an interrupt signal handler allows graceful shutdown of the program. - Use exit code 130 for interrupt signals. Fixes: https://github.com/kubescape/kubescape/issues/1476 Signed-off-by: VaibhavMalik4187 --- main.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/main.go b/main.go index f1dbf940..ed1ea641 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,29 @@ package main import ( + "os" + "os/signal" + "syscall" + logger "github.com/kubescape/go-logger" "github.com/kubescape/kubescape/v3/cmd" ) func main() { + // Capture interrupt signal + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) + + // Handle interrupt signal + go func() { + <-signalChan + // Perform cleanup or graceful shutdown here + logger.L().StopError("Received interrupt signal, exiting...") + + // Exit the program with proper exit code for SIGINT + os.Exit(130) + }() + if err := cmd.Execute(); err != nil { logger.L().Fatal(err.Error()) }