mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-06-24 07:14:15 +00:00
added tap acceptance tests, fixed duplicate namespace problem (#244)
This commit is contained in:
parent
3308cab826
commit
101a54e8da
46
.github/workflows/pr_validation.yml
vendored
46
.github/workflows/pr_validation.yml
vendored
@ -1,9 +1,15 @@
|
|||||||
name: PR validation
|
name: PR validation
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'develop'
|
- 'develop'
|
||||||
- 'main'
|
- 'main'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: mizu-pr-validation-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-cli:
|
build-cli:
|
||||||
name: Build CLI
|
name: Build CLI
|
||||||
@ -38,43 +44,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Agent
|
- name: Build Agent
|
||||||
run: make agent
|
run: make agent
|
||||||
|
|
||||||
run-tests-cli:
|
|
||||||
name: Run CLI tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Set up Go 1.16
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: '^1.16'
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: make test-cli
|
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
|
||||||
uses: codecov/codecov-action@v2
|
|
||||||
|
|
||||||
run-tests-agent:
|
|
||||||
name: Run Agent tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Set up Go 1.16
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: '^1.16'
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- shell: bash
|
|
||||||
run: |
|
|
||||||
sudo apt-get install libpcap-dev
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: make test-agent
|
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
|
||||||
uses: codecov/codecov-action@v2
|
|
||||||
|
56
.github/workflows/tests_validation.yml
vendored
Normal file
56
.github/workflows/tests_validation.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
name: tests validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
|
- 'main'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: mizu-tests-validation-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-tests-cli:
|
||||||
|
name: Run CLI tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.16
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '^1.16'
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: make test-cli
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v2
|
||||||
|
|
||||||
|
run-tests-agent:
|
||||||
|
name: Run Agent tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.16
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '^1.16'
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- shell: bash
|
||||||
|
run: |
|
||||||
|
sudo apt-get install libpcap-dev
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: make test-agent
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v2
|
@ -1,2 +1,2 @@
|
|||||||
test: ## Run acceptance tests.
|
test: ## Run acceptance tests.
|
||||||
@go test ./...
|
@go test ./... -timeout 1h
|
||||||
|
283
acceptanceTests/config_test.go
Normal file
283
acceptanceTests/config_test.go
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
package acceptanceTests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tapConfig struct {
|
||||||
|
GuiPort uint16 `yaml:"gui-port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type configStruct struct {
|
||||||
|
Tap tapConfig `yaml:"tap"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigRegenerate(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, configPathErr := getConfigPath()
|
||||||
|
if configPathErr != nil {
|
||||||
|
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configCmdArgs := getDefaultConfigCommandArgs()
|
||||||
|
|
||||||
|
configCmdArgs = append(configCmdArgs, "-r")
|
||||||
|
|
||||||
|
configCmd := exec.Command(cliPath, configCmdArgs...)
|
||||||
|
t.Logf("running command: %v", configCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := os.Remove(configPath); err != nil {
|
||||||
|
t.Logf("failed to delete config file, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := configCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start config command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := configCmd.Wait(); err != nil {
|
||||||
|
t.Errorf("failed to wait config command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, readFileErr := ioutil.ReadFile(configPath)
|
||||||
|
if readFileErr != nil {
|
||||||
|
t.Errorf("failed to read config file, err: %v", readFileErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigGuiPort(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []uint16{8898}
|
||||||
|
|
||||||
|
for _, guiPort := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", guiPort), func(t *testing.T) {
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, configPathErr := getConfigPath()
|
||||||
|
if configPathErr != nil {
|
||||||
|
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := configStruct{}
|
||||||
|
config.Tap.GuiPort = guiPort
|
||||||
|
|
||||||
|
configBytes, marshalErr := yaml.Marshal(config)
|
||||||
|
if marshalErr != nil {
|
||||||
|
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||||
|
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(configPath); err != nil {
|
||||||
|
t.Logf("failed to delete config file, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(guiPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigSetGuiPort(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
ConfigFileGuiPort uint16
|
||||||
|
SetGuiPort uint16
|
||||||
|
}{
|
||||||
|
{ConfigFileGuiPort: 8898, SetGuiPort: 8897},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, guiPortStruct := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", guiPortStruct.SetGuiPort), func(t *testing.T) {
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, configPathErr := getConfigPath()
|
||||||
|
if configPathErr != nil {
|
||||||
|
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := configStruct{}
|
||||||
|
config.Tap.GuiPort = guiPortStruct.ConfigFileGuiPort
|
||||||
|
|
||||||
|
configBytes, marshalErr := yaml.Marshal(config)
|
||||||
|
if marshalErr != nil {
|
||||||
|
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||||
|
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "--set", fmt.Sprintf("tap.gui-port=%v", guiPortStruct.SetGuiPort))
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(configPath); err != nil {
|
||||||
|
t.Logf("failed to delete config file, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(guiPortStruct.SetGuiPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigFlagGuiPort(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
ConfigFileGuiPort uint16
|
||||||
|
FlagGuiPort uint16
|
||||||
|
}{
|
||||||
|
{ConfigFileGuiPort: 8898, FlagGuiPort: 8896},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, guiPortStruct := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", guiPortStruct.FlagGuiPort), func(t *testing.T) {
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, configPathErr := getConfigPath()
|
||||||
|
if configPathErr != nil {
|
||||||
|
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := configStruct{}
|
||||||
|
config.Tap.GuiPort = guiPortStruct.ConfigFileGuiPort
|
||||||
|
|
||||||
|
configBytes, marshalErr := yaml.Marshal(config)
|
||||||
|
if marshalErr != nil {
|
||||||
|
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||||
|
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "-p", fmt.Sprintf("%v", guiPortStruct.FlagGuiPort))
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(configPath); err != nil {
|
||||||
|
t.Logf("failed to delete config file, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(guiPortStruct.FlagGuiPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
module github.com/up9inc/mizu/tests
|
module github.com/up9inc/mizu/tests
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
|
require gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
|
4
acceptanceTests/go.sum
Normal file
4
acceptanceTests/go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
@ -26,14 +26,21 @@ fi
|
|||||||
echo "Starting minikube..."
|
echo "Starting minikube..."
|
||||||
minikube start
|
minikube start
|
||||||
|
|
||||||
echo "Creating mizu tests namespace"
|
echo "Creating mizu tests namespaces"
|
||||||
kubectl create namespace mizu-tests
|
kubectl create namespace mizu-tests
|
||||||
|
kubectl create namespace mizu-tests2
|
||||||
|
|
||||||
echo "Creating httpbin deployment"
|
echo "Creating httpbin deployments"
|
||||||
kubectl create deployment httpbin --image=kennethreitz/httpbin -n mizu-tests
|
kubectl create deployment httpbin --image=kennethreitz/httpbin -n mizu-tests
|
||||||
|
kubectl create deployment httpbin2 --image=kennethreitz/httpbin -n mizu-tests
|
||||||
|
|
||||||
echo "Creating httpbin service"
|
kubectl create deployment httpbin --image=kennethreitz/httpbin -n mizu-tests2
|
||||||
|
|
||||||
|
echo "Creating httpbin services"
|
||||||
kubectl expose deployment httpbin --type=NodePort --port=80 -n mizu-tests
|
kubectl expose deployment httpbin --type=NodePort --port=80 -n mizu-tests
|
||||||
|
kubectl expose deployment httpbin2 --type=NodePort --port=80 -n mizu-tests
|
||||||
|
|
||||||
|
kubectl expose deployment httpbin --type=NodePort --port=80 -n mizu-tests2
|
||||||
|
|
||||||
echo "Starting proxy"
|
echo "Starting proxy"
|
||||||
kubectl proxy --port=8080 &
|
kubectl proxy --port=8080 &
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package acceptanceTests
|
package acceptanceTests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -24,6 +28,10 @@ func TestTapAndFetch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tapCmdArgs := getDefaultTapCommandArgs()
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
t.Logf("running command: %v", tapCmd.String())
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
@ -38,14 +46,16 @@ func TestTapAndFetch(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := waitTapPodsReady(); err != nil {
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyUrl := "http://localhost:8080/api/v1/namespaces/mizu-tests/services/httpbin/proxy/get"
|
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||||
for i := 0; i < entriesCount; i++ {
|
for i := 0; i < entriesCount; i++ {
|
||||||
if _, requestErr := executeHttpRequest(proxyUrl); requestErr != nil {
|
if _, requestErr := executeHttpGetRequest(fmt.Sprintf("%v/get", proxyUrl)); requestErr != nil {
|
||||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -54,28 +64,22 @@ func TestTapAndFetch(t *testing.T) {
|
|||||||
entriesCheckFunc := func() error {
|
entriesCheckFunc := func() error {
|
||||||
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
entriesUrl := fmt.Sprintf("http://localhost:8899/mizu/api/entries?limit=%v&operator=lt×tamp=%v", entriesCount, timestamp)
|
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, entriesCount, timestamp)
|
||||||
requestResult, requestErr := executeHttpRequest(entriesUrl)
|
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||||
if requestErr != nil {
|
if requestErr != nil {
|
||||||
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, ok := requestResult.([]interface{})
|
entries := requestResult.([]interface{})
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid entries type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
||||||
}
|
}
|
||||||
|
|
||||||
entry, ok := entries[0].(map[string]interface{})
|
entry := entries[0].(map[string]interface{})
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid entry type")
|
|
||||||
}
|
|
||||||
|
|
||||||
entryUrl := fmt.Sprintf("http://localhost:8899/mizu/api/entries/%v", entry["id"])
|
entryUrl := fmt.Sprintf("%v/api/entries/%v", apiServerUrl, entry["id"])
|
||||||
requestResult, requestErr = executeHttpRequest(entryUrl)
|
requestResult, requestErr = executeHttpGetRequest(entryUrl)
|
||||||
if requestErr != nil {
|
if requestErr != nil {
|
||||||
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||||
}
|
}
|
||||||
@ -86,7 +90,7 @@ func TestTapAndFetch(t *testing.T) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := retriesExecute(ShortRetriesCount, entriesCheckFunc); err != nil {
|
if err := retriesExecute(shortRetriesCount, entriesCheckFunc); err != nil {
|
||||||
t.Errorf("%v", err)
|
t.Errorf("%v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -117,10 +121,634 @@ func TestTapAndFetch(t *testing.T) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := retriesExecute(ShortRetriesCount, harCheckFunc); err != nil {
|
if err := retriesExecute(shortRetriesCount, harCheckFunc); err != nil {
|
||||||
t.Errorf("%v", err)
|
t.Errorf("%v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTapGuiPort(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []uint16{8898}
|
||||||
|
|
||||||
|
for _, guiPort := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", guiPort), func(t *testing.T) {
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "-p", fmt.Sprintf("%d", guiPort))
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(guiPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapAllNamespaces(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPods := []struct{
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
}{
|
||||||
|
{Name: "httpbin", Namespace: "mizu-tests"},
|
||||||
|
{Name: "httpbin", Namespace: "mizu-tests2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "-A")
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
podsUrl := fmt.Sprintf("%v/api/tapStatus", apiServerUrl)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(podsUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
t.Errorf("failed to get tap status, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pods, err := getPods(requestResult)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get pods, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expectedPod := range expectedPods {
|
||||||
|
podFound := false
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
podNamespace := pod["namespace"].(string)
|
||||||
|
podName := pod["name"].(string)
|
||||||
|
|
||||||
|
if expectedPod.Namespace == podNamespace && strings.Contains(podName, expectedPod.Name) {
|
||||||
|
podFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !podFound {
|
||||||
|
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapMultipleNamespaces(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPods := []struct{
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
}{
|
||||||
|
{Name: "httpbin", Namespace: "mizu-tests"},
|
||||||
|
{Name: "httpbin2", Namespace: "mizu-tests"},
|
||||||
|
{Name: "httpbin", Namespace: "mizu-tests2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
var namespacesCmd []string
|
||||||
|
for _, expectedPod := range expectedPods {
|
||||||
|
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
|
||||||
|
}
|
||||||
|
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
podsUrl := fmt.Sprintf("%v/api/tapStatus", apiServerUrl)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(podsUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
t.Errorf("failed to get tap status, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pods, err := getPods(requestResult)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get pods, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedPods) != len(pods) {
|
||||||
|
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expectedPod := range expectedPods {
|
||||||
|
podFound := false
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
podNamespace := pod["namespace"].(string)
|
||||||
|
podName := pod["name"].(string)
|
||||||
|
|
||||||
|
if expectedPod.Namespace == podNamespace && strings.Contains(podName, expectedPod.Name) {
|
||||||
|
podFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !podFound {
|
||||||
|
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapRegex(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
regexPodName := "httpbin2"
|
||||||
|
expectedPods := []struct{
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
}{
|
||||||
|
{Name: regexPodName, Namespace: "mizu-tests"},
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgsWithRegex(regexPodName)
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
podsUrl := fmt.Sprintf("%v/api/tapStatus", apiServerUrl)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(podsUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
t.Errorf("failed to get tap status, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pods, err := getPods(requestResult)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get pods, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedPods) != len(pods) {
|
||||||
|
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expectedPod := range expectedPods {
|
||||||
|
podFound := false
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
podNamespace := pod["namespace"].(string)
|
||||||
|
podName := pod["name"].(string)
|
||||||
|
|
||||||
|
if expectedPod.Namespace == podNamespace && strings.Contains(podName, expectedPod.Name) {
|
||||||
|
podFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !podFound {
|
||||||
|
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapDryRun(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "--dry-run")
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resultChannel := make(chan string, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := tapCmd.Wait(); err != nil {
|
||||||
|
resultChannel <- "fail"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultChannel <- "success"
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(shortRetriesCount * time.Second)
|
||||||
|
resultChannel <- "fail"
|
||||||
|
}()
|
||||||
|
|
||||||
|
testResult := <- resultChannel
|
||||||
|
if testResult != "success" {
|
||||||
|
t.Errorf("unexpected result - dry run cmd not done")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapRedact(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||||
|
requestBody := map[string]string{"User": "Mizu"}
|
||||||
|
for i := 0; i < defaultEntriesCount; i++ {
|
||||||
|
if _, requestErr := executeHttpPostRequest(fmt.Sprintf("%v/post", proxyUrl), requestBody); requestErr != nil {
|
||||||
|
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redactCheckFunc := func() error {
|
||||||
|
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, defaultEntriesCount, timestamp)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := requestResult.([]interface{})
|
||||||
|
firstEntry := entries[0].(map[string]interface{})
|
||||||
|
|
||||||
|
entryUrl := fmt.Sprintf("%v/api/entries/%v", apiServerUrl, firstEntry["id"])
|
||||||
|
requestResult, requestErr = executeHttpGetRequest(entryUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := requestResult.(map[string]interface{})["entry"].(map[string]interface{})
|
||||||
|
entryRequest := entry["request"].(map[string]interface{})
|
||||||
|
|
||||||
|
headers := entryRequest["headers"].([]interface{})
|
||||||
|
for _, headerInterface := range headers {
|
||||||
|
header := headerInterface.(map[string]interface{})
|
||||||
|
if header["name"].(string) != "User-Agent" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userAgent := header["value"].(string)
|
||||||
|
if userAgent != "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - user agent is not redacted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := entryRequest["postData"].(map[string]interface{})
|
||||||
|
textDataStr := data["text"].(string)
|
||||||
|
|
||||||
|
var textData map[string]string
|
||||||
|
if parseErr := json.Unmarshal([]byte(textDataStr), &textData); parseErr != nil {
|
||||||
|
return fmt.Errorf("failed to parse text data, err: %v", parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if textData["User"] != "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - user in body is not redacted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := retriesExecute(shortRetriesCount, redactCheckFunc); err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapNoRedact(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "--no-redact")
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||||
|
requestBody := map[string]string{"User": "Mizu"}
|
||||||
|
for i := 0; i < defaultEntriesCount; i++ {
|
||||||
|
if _, requestErr := executeHttpPostRequest(fmt.Sprintf("%v/post", proxyUrl), requestBody); requestErr != nil {
|
||||||
|
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redactCheckFunc := func() error {
|
||||||
|
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, defaultEntriesCount, timestamp)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := requestResult.([]interface{})
|
||||||
|
firstEntry := entries[0].(map[string]interface{})
|
||||||
|
|
||||||
|
entryUrl := fmt.Sprintf("%v/api/entries/%v", apiServerUrl, firstEntry["id"])
|
||||||
|
requestResult, requestErr = executeHttpGetRequest(entryUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := requestResult.(map[string]interface{})["entry"].(map[string]interface{})
|
||||||
|
entryRequest := entry["request"].(map[string]interface{})
|
||||||
|
|
||||||
|
headers := entryRequest["headers"].([]interface{})
|
||||||
|
for _, headerInterface := range headers {
|
||||||
|
header := headerInterface.(map[string]interface{})
|
||||||
|
if header["name"].(string) != "User-Agent" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userAgent := header["value"].(string)
|
||||||
|
if userAgent == "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - user agent is redacted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := entryRequest["postData"].(map[string]interface{})
|
||||||
|
textDataStr := data["text"].(string)
|
||||||
|
|
||||||
|
var textData map[string]string
|
||||||
|
if parseErr := json.Unmarshal([]byte(textDataStr), &textData); parseErr != nil {
|
||||||
|
return fmt.Errorf("failed to parse text data, err: %v", parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if textData["User"] == "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - user in body is redacted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := retriesExecute(shortRetriesCount, redactCheckFunc); err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapRegexMasking(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "-r", "Mizu")
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||||
|
for i := 0; i < defaultEntriesCount; i++ {
|
||||||
|
response, requestErr := http.Post(fmt.Sprintf("%v/post", proxyUrl), "text/plain", bytes.NewBufferString("Mizu"))
|
||||||
|
if _, requestErr = executeHttpRequest(response, requestErr); requestErr != nil {
|
||||||
|
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redactCheckFunc := func() error {
|
||||||
|
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, defaultEntriesCount, timestamp)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := requestResult.([]interface{})
|
||||||
|
firstEntry := entries[0].(map[string]interface{})
|
||||||
|
|
||||||
|
entryUrl := fmt.Sprintf("%v/api/entries/%v", apiServerUrl, firstEntry["id"])
|
||||||
|
requestResult, requestErr = executeHttpGetRequest(entryUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := requestResult.(map[string]interface{})["entry"].(map[string]interface{})
|
||||||
|
entryRequest := entry["request"].(map[string]interface{})
|
||||||
|
|
||||||
|
data := entryRequest["postData"].(map[string]interface{})
|
||||||
|
textData := data["text"].(string)
|
||||||
|
|
||||||
|
if textData != "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - body is not redacted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := retriesExecute(shortRetriesCount, redactCheckFunc); err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package acceptanceTests
|
package acceptanceTests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -14,8 +14,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LongRetriesCount = 100
|
longRetriesCount = 100
|
||||||
ShortRetriesCount = 10
|
shortRetriesCount = 10
|
||||||
|
defaultApiServerPort = 8899
|
||||||
|
defaultNamespaceName = "mizu-tests"
|
||||||
|
defaultServiceName = "httpbin"
|
||||||
|
defaultEntriesCount = 50
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCliPath() (string, error) {
|
func getCliPath() (string, error) {
|
||||||
@ -28,33 +32,64 @@ func getCliPath() (string, error) {
|
|||||||
return cliPath, nil
|
return cliPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getConfigPath() (string, error) {
|
||||||
|
home, homeDirErr := os.UserHomeDir()
|
||||||
|
if homeDirErr != nil {
|
||||||
|
return "", homeDirErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(home, ".mizu", "config.yaml"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProxyUrl(namespace string, service string) string {
|
||||||
|
return fmt.Sprintf("http://localhost:8080/api/v1/namespaces/%v/services/%v/proxy", namespace, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getApiServerUrl(port uint16) string {
|
||||||
|
return fmt.Sprintf("http://localhost:%v/mizu", port)
|
||||||
|
}
|
||||||
|
|
||||||
func getDefaultCommandArgs() []string {
|
func getDefaultCommandArgs() []string {
|
||||||
setFlag := "--set"
|
setFlag := "--set"
|
||||||
telemetry := "telemetry=false"
|
telemetry := "telemetry=false"
|
||||||
|
agentImage := "agent-image=gcr.io/up9-docker-hub/mizu/ci:0.0.0"
|
||||||
|
imagePullPolicy := "image-pull-policy=Never"
|
||||||
|
|
||||||
return []string{setFlag, telemetry}
|
return []string{setFlag, telemetry, setFlag, agentImage, setFlag, imagePullPolicy}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultTapCommandArgs() []string {
|
func getDefaultTapCommandArgs() []string {
|
||||||
tapCommand := "tap"
|
tapCommand := "tap"
|
||||||
setFlag := "--set"
|
|
||||||
namespaces := "tap.namespaces=mizu-tests"
|
|
||||||
agentImage := "agent-image=gcr.io/up9-docker-hub/mizu/ci:0.0.0"
|
|
||||||
imagePullPolicy := "image-pull-policy=Never"
|
|
||||||
|
|
||||||
defaultCmdArgs := getDefaultCommandArgs()
|
|
||||||
|
|
||||||
return append([]string{tapCommand, setFlag, namespaces, setFlag, agentImage, setFlag, imagePullPolicy}, defaultCmdArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultFetchCommandArgs() []string {
|
|
||||||
tapCommand := "fetch"
|
|
||||||
|
|
||||||
defaultCmdArgs := getDefaultCommandArgs()
|
defaultCmdArgs := getDefaultCommandArgs()
|
||||||
|
|
||||||
return append([]string{tapCommand}, defaultCmdArgs...)
|
return append([]string{tapCommand}, defaultCmdArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDefaultTapCommandArgsWithRegex(regex string) []string {
|
||||||
|
tapCommand := "tap"
|
||||||
|
defaultCmdArgs := getDefaultCommandArgs()
|
||||||
|
|
||||||
|
return append([]string{tapCommand, regex}, defaultCmdArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultTapNamespace() []string {
|
||||||
|
return []string{"-n", "mizu-tests"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultFetchCommandArgs() []string {
|
||||||
|
fetchCommand := "fetch"
|
||||||
|
defaultCmdArgs := getDefaultCommandArgs()
|
||||||
|
|
||||||
|
return append([]string{fetchCommand}, defaultCmdArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultConfigCommandArgs() []string {
|
||||||
|
configCommand := "config"
|
||||||
|
defaultCmdArgs := getDefaultCommandArgs()
|
||||||
|
|
||||||
|
return append([]string{configCommand}, defaultCmdArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
func retriesExecute(retriesCount int, executeFunc func() error) error {
|
func retriesExecute(retriesCount int, executeFunc func() error) error {
|
||||||
var lastError error
|
var lastError error
|
||||||
|
|
||||||
@ -72,19 +107,15 @@ func retriesExecute(retriesCount int, executeFunc func() error) error {
|
|||||||
return fmt.Errorf("reached max retries count, retries count: %v, last err: %v", retriesCount, lastError)
|
return fmt.Errorf("reached max retries count, retries count: %v, last err: %v", retriesCount, lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitTapPodsReady() error {
|
func waitTapPodsReady(apiServerUrl string) error {
|
||||||
resolvingUrl := fmt.Sprintf("http://localhost:8899/mizu/status/tappersCount")
|
resolvingUrl := fmt.Sprintf("%v/status/tappersCount", apiServerUrl)
|
||||||
tapPodsReadyFunc := func() error {
|
tapPodsReadyFunc := func() error {
|
||||||
requestResult, requestErr := executeHttpRequest(resolvingUrl)
|
requestResult, requestErr := executeHttpGetRequest(resolvingUrl)
|
||||||
if requestErr != nil {
|
if requestErr != nil {
|
||||||
return requestErr
|
return requestErr
|
||||||
}
|
}
|
||||||
|
|
||||||
tappersCount, ok := requestResult.(float64)
|
tappersCount := requestResult.(float64)
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid tappers count type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tappersCount == 0 {
|
if tappersCount == 0 {
|
||||||
return fmt.Errorf("no tappers running")
|
return fmt.Errorf("no tappers running")
|
||||||
}
|
}
|
||||||
@ -92,7 +123,7 @@ func waitTapPodsReady() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return retriesExecute(LongRetriesCount, tapPodsReadyFunc)
|
return retriesExecute(longRetriesCount, tapPodsReadyFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonBytesToInterface(jsonBytes []byte) (interface{}, error) {
|
func jsonBytesToInterface(jsonBytes []byte) (interface{}, error) {
|
||||||
@ -104,8 +135,7 @@ func jsonBytesToInterface(jsonBytes []byte) (interface{}, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeHttpRequest(url string) (interface{}, error) {
|
func executeHttpRequest(response *http.Response, requestErr error) (interface{}, error) {
|
||||||
response, requestErr := http.Get(url)
|
|
||||||
if requestErr != nil {
|
if requestErr != nil {
|
||||||
return nil, requestErr
|
return nil, requestErr
|
||||||
} else if response.StatusCode != 200 {
|
} else if response.StatusCode != 200 {
|
||||||
@ -122,6 +152,21 @@ func executeHttpRequest(url string) (interface{}, error) {
|
|||||||
return jsonBytesToInterface(data)
|
return jsonBytesToInterface(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func executeHttpGetRequest(url string) (interface{}, error) {
|
||||||
|
response, requestErr := http.Get(url)
|
||||||
|
return executeHttpRequest(response, requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeHttpPostRequest(url string, body interface{}) (interface{}, error) {
|
||||||
|
requestBody, jsonErr := json.Marshal(body)
|
||||||
|
if jsonErr != nil {
|
||||||
|
return nil, jsonErr
|
||||||
|
}
|
||||||
|
|
||||||
|
response, requestErr := http.Post(url, "application/json", bytes.NewBuffer(requestBody))
|
||||||
|
return executeHttpRequest(response, requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
func cleanupCommand(cmd *exec.Cmd) error {
|
func cleanupCommand(cmd *exec.Cmd) error {
|
||||||
if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil {
|
if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -134,26 +179,27 @@ func cleanupCommand(cmd *exec.Cmd) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEntriesFromHarBytes(harBytes []byte) ([]interface{}, error){
|
func getEntriesFromHarBytes(harBytes []byte) ([]interface{}, error) {
|
||||||
harInterface, convertErr := jsonBytesToInterface(harBytes)
|
harInterface, convertErr := jsonBytesToInterface(harBytes)
|
||||||
if convertErr != nil {
|
if convertErr != nil {
|
||||||
return nil, convertErr
|
return nil, convertErr
|
||||||
}
|
}
|
||||||
|
|
||||||
har, ok := harInterface.(map[string]interface{})
|
har := harInterface.(map[string]interface{})
|
||||||
if !ok {
|
harLog := har["log"].(map[string]interface{})
|
||||||
return nil, errors.New("invalid har type")
|
harEntries := harLog["entries"].([]interface{})
|
||||||
}
|
|
||||||
|
|
||||||
harLog, ok := har["log"].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("invalid har log type")
|
|
||||||
}
|
|
||||||
|
|
||||||
harEntries, ok := harLog["entries"].([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("invalid har entries type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return harEntries, nil
|
return harEntries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPods(tapStatusInterface interface{}) ([]map[string]interface{}, error) {
|
||||||
|
tapStatus := tapStatusInterface.(map[string]interface{})
|
||||||
|
podsInterface := tapStatus["pods"].([]interface{})
|
||||||
|
|
||||||
|
var pods []map[string]interface{}
|
||||||
|
for _, podInterface := range podsInterface {
|
||||||
|
pods = append(pods, podInterface.(map[string]interface{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return pods, nil
|
||||||
|
}
|
||||||
|
@ -561,7 +561,7 @@ func getNamespaces(kubernetesProvider *kubernetes.Provider) []string {
|
|||||||
if config.Config.Tap.AllNamespaces {
|
if config.Config.Tap.AllNamespaces {
|
||||||
return []string{mizu.K8sAllNamespaces}
|
return []string{mizu.K8sAllNamespaces}
|
||||||
} else if len(config.Config.Tap.Namespaces) > 0 {
|
} else if len(config.Config.Tap.Namespaces) > 0 {
|
||||||
return config.Config.Tap.Namespaces
|
return mizu.Unique(config.Config.Tap.Namespaces)
|
||||||
} else {
|
} else {
|
||||||
return []string{kubernetesProvider.CurrentNamespace()}
|
return []string{kubernetesProvider.CurrentNamespace()}
|
||||||
}
|
}
|
||||||
|
@ -9,3 +9,17 @@ func Contains(slice []string, containsValue string) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Unique(slice []string) []string {
|
||||||
|
keys := make(map[string]bool)
|
||||||
|
var list []string
|
||||||
|
|
||||||
|
for _, entry := range slice {
|
||||||
|
if _, value := keys[entry]; !value {
|
||||||
|
keys[entry] = true
|
||||||
|
list = append(list, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package mizu_test
|
package mizu_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -88,3 +90,41 @@ func TestContainsNilSlice(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUniqueNoDuplicateValues(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Slice []string
|
||||||
|
Expected []string
|
||||||
|
}{
|
||||||
|
{Slice: []string{"apple", "orange", "banana", "grapes"}, Expected: []string{"apple", "orange", "banana", "grapes"}},
|
||||||
|
{Slice: []string{"dog", "cat", "mouse"}, Expected: []string{"dog", "cat", "mouse"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%v", index), func(t *testing.T) {
|
||||||
|
actual := mizu.Unique(test.Slice)
|
||||||
|
if !reflect.DeepEqual(test.Expected, actual) {
|
||||||
|
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUniqueDuplicateValues(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Slice []string
|
||||||
|
Expected []string
|
||||||
|
}{
|
||||||
|
{Slice: []string{"apple", "apple", "orange", "orange", "banana", "banana", "grapes", "grapes"}, Expected: []string{"apple", "orange", "banana", "grapes"}},
|
||||||
|
{Slice: []string{"dog", "cat", "cat", "mouse"}, Expected: []string{"dog", "cat", "mouse"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%v", index), func(t *testing.T) {
|
||||||
|
actual := mizu.Unique(test.Slice)
|
||||||
|
if !reflect.DeepEqual(test.Expected, actual) {
|
||||||
|
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user