Skip to content
Merged

Dev #17

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/cli/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var cmdPlanCheck = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
opts, err := parseCheckPlanFlags(cmd, args)
if err != nil {
cli.Errorf("Failed to parse provided values: %v", err)
cli.Errorf("Failed to parse provided save: %v", err)
return
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/generator/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var Cmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
opts, err := parseOptions(cmd)
if err != nil {
cli.Errorf("Failed to parse provided values: %v", err)
cli.Errorf("Failed to parse provided save: %v", err)
return
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var Cmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
opts, err := parseOptions(cmd)
if err != nil {
cli.Errorf("Failed to parse provided values: %v", err)
cli.Errorf("Failed to parse provided save: %v", err)
return
}

Expand Down
2 changes: 1 addition & 1 deletion examples/plan/plan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ metadata:
spec:
stages:
- name: "Preparation..."
description: "First stage with loading values to context"
description: "First stage with loading save to context"
manifests:
- Values.simple-value
- name: "Starting and checking server"
Expand Down
26 changes: 26 additions & 0 deletions examples/simple-http-tests-1/http_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: v1
kind: HttpTest
metadata:
name: simple-test-example-1
namespace: simple-http-tests-1

spec:
target: http://127.0.0.1:8081
cases:

- name: Create New Array of User
method: POST
endpoint: /users-batch
assert:
- target: status
equals: 201
body:
users:
__repeat: 2
__template:
name: "{{ Fake.name }}"
email: "{{ Fake.email }}"
age: "{{ Fake.uint.10.100 }}"
address:
street: "{{ Fake.address }}"
number: "{{ Regex(\"^[a-z]{5,10}@[a-z]{5,10}\\.(com|net|org)$\") }}"
2 changes: 1 addition & 1 deletion examples/values/values.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: v1
kind: Values
metadata:
name: simple-values
name: simple-save
spec:
users:
username: ["Max", "Carl", "John", "Alex", "Uli"]
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.18.0
golang.org/x/text v0.23.0
gopkg.in/yaml.v3 v3.0.1
)

Expand Down Expand Up @@ -96,6 +97,5 @@ require (
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
)
32 changes: 32 additions & 0 deletions internal/collections/priority_queue_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package collections

import (
"slices"
"sort"
"testing"

"github.com/stretchr/testify/require"
)

func TestPriorityQueue(t *testing.T) {
pq := NewPriorityQueue(func(a, b int) bool {
return a > b
})

values := []int{
1, 2, 3, 10, 3, 145, 94, 173, 833,
}

for _, v := range values {
pq.Push(v)
}

sort.Ints(values)
slices.Reverse(values)

require.Equal(t, len(values), pq.Len())

for _, x := range values {
require.Equal(t, x, pq.Pop())
}
}
15 changes: 8 additions & 7 deletions internal/core/manifests/kinds/tests/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type HttpCase struct {
Body map[string]any `yaml:"body,omitempty" json:"body,omitempty" validate:"omitempty,min=1,max=100"`
Assert []*Assert `yaml:"assert,omitempty" json:"assert,omitempty" validate:"omitempty,min=1,max=50,dive"`
Save *Save `yaml:"save,omitempty" json:"save,omitempty" validate:"omitempty"`
Pass []Pass `yaml:"pass,omitempty" json:"pass,omitempty" validate:"omitempty,min=1,max=25,dive"`
Pass []*Pass `yaml:"pass,omitempty" json:"pass,omitempty" validate:"omitempty,min=1,max=25,dive"`
Timeout time.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty" validate:"omitempty,duration"`
Parallel bool `yaml:"async,omitempty" json:"async,omitempty" validate:"omitempty,boolean"`
Details []string `yaml:"details,omitempty" json:"details,omitempty" validate:"omitempty,min=1,max=100"`
Expand All @@ -28,12 +28,13 @@ type Assert struct {
}

type Save struct {
Json map[string]string `yaml:"json,omitempty" json:"json,omitempty" validate:"omitempty,dive,keys,endkeys"`
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty" validate:"omitempty,dive,keys,endkeys"`
Status bool `yaml:"status,omitempty" json:"status,omitempty" validate:"omitempty,boolean"`
Body bool `yaml:"body,omitempty" json:"body,omitempty" validate:"omitempty,boolean"`
All bool `yaml:"all,omitempty" json:"all,omitempty" validate:"omitempty,boolean"`
Group string `yaml:"group,omitempty" json:"group,omitempty" validate:"omitempty,min=1,max=100"`
Request *SaveEntry `yaml:"request,omitempty" json:"request,omitempty" validate:"omitempty"`
Response *SaveEntry `yaml:"response,omitempty" json:"response,omitempty" validate:"omitempty"`
}

type SaveEntry struct {
Body map[string]string `yaml:"body,omitempty" json:"body,omitempty" validate:"omitempty,min=1,max=20,dive,keys,endkeys"`
Headers []string `yaml:"headers,omitempty" json:"headers,omitempty" validate:"omitempty,min=1,max=20"`
}

type Pass struct {
Expand Down
24 changes: 14 additions & 10 deletions internal/core/runner/assert/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package assert
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
Expand Down Expand Up @@ -37,29 +38,32 @@ func NewRunner() *Runner {
}

func (r *Runner) Assert(ctx interfaces.ExecutionContext, asserts []*tests.Assert, resp *http.Response, body []byte) error {
var err error

for _, assert := range asserts {
switch assert.Target {
case Status.String():
return r.assertStatus(ctx, assert, resp)
err = errors.Join(err, r.assertStatus(ctx, assert, resp))
case Body.String():
return r.assertBody(ctx, assert, resp, body)
err = errors.Join(err, r.assertBody(ctx, assert, resp, body))
case Headers.String():
return r.assertHeaders(ctx, assert, resp)
err = errors.Join(err, r.assertHeaders(ctx, assert, resp))
default:
return fmt.Errorf("assert failed: unknown assert target %s", assert.Target)
}
}
return nil

return err
}

func (r *Runner) assertStatus(_ interfaces.ExecutionContext, assert *tests.Assert, resp *http.Response) error {
if assert.Equals != nil {
expectedCode, ok := assert.Equals.(float64)
expectedCode, ok := assert.Equals.(int)
if !ok {
return fmt.Errorf("expected status correct value got %v", assert.Equals)
return fmt.Errorf("expected status type [int] got %T", assert.Equals)
}

if resp.StatusCode != int(expectedCode) {
if resp.StatusCode != expectedCode {
return fmt.Errorf("expected status code %v, got %v", expectedCode, resp.StatusCode)
}
}
Expand Down Expand Up @@ -114,7 +118,7 @@ func (r *Runner) assertBody(_ interfaces.ExecutionContext, assert *tests.Assert,

if assert.Contains != "" {
if !bytes.Contains(body, []byte(assert.Contains)) {
return fmt.Errorf("expected %v in body", assert.Contains)
return fmt.Errorf("expected '%v' in body", assert.Contains)
}

return nil
Expand All @@ -126,7 +130,7 @@ func (r *Runner) assertBody(_ interfaces.ExecutionContext, assert *tests.Assert,
func (r *Runner) assertHeaders(_ interfaces.ExecutionContext, assert *tests.Assert, resp *http.Response) error {
if assert.Equals != nil {
if equals, ok := assert.Equals.(map[string]any); ok {
return fmt.Errorf("expected map assertion got %v", assert.Equals)
return fmt.Errorf("expected map type assertion got %T", assert.Equals)
} else {
for key, expectedVal := range equals {
actualVal := resp.Header.Get(key)
Expand All @@ -152,7 +156,7 @@ func (r *Runner) assertHeaders(_ interfaces.ExecutionContext, assert *tests.Asse
}

if !found {
return fmt.Errorf("expected header %v but not found", assert.Contains)
return fmt.Errorf("expected header contains %v but not found", assert.Contains)
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/core/runner/cli/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (o *Output) DumpValues(values map[string]any) {
rows = append(rows, fmt.Sprintf("%v: %v", k, v))
}

cli.Printf("Damping values: \n%s", strings.Join(rows, "\n"))
cli.Printf("Damping save: \n%s", strings.Join(rows, "\n"))
}
}

Expand Down
7 changes: 3 additions & 4 deletions internal/core/runner/depends/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import (
)

var priorityOrder = map[string]int{
"Values": 100,
"ConfigMap": 90,
"Target": 50,
"Service": 30,
manifests.ValuesKind: 100,
manifests.ServerKind: 40,
manifests.ServiceKind: 30,
}

type GraphResult struct {
Expand Down
66 changes: 36 additions & 30 deletions internal/core/runner/executor/executors/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,26 @@ import (
"github.com/apiqube/cli/internal/core/runner/assert"
"github.com/apiqube/cli/internal/core/runner/form"
"github.com/apiqube/cli/internal/core/runner/interfaces"
"github.com/apiqube/cli/internal/core/runner/values"
"github.com/apiqube/cli/internal/core/runner/save"
)

const httpExecutorOutputPrefix = "HTTP Executor:"

const httpExecutorRunTimeout = time.Second * 30

var _ interfaces.Executor = (*HTTPExecutor)(nil)

type HTTPExecutor struct {
client *http.Client
extractor *values.Extractor
extractor *save.Extractor
assertor *assert.Runner
passer *form.Runner
}

func NewHTTPExecutor() *HTTPExecutor {
return &HTTPExecutor{
client: &http.Client{Timeout: 30 * time.Second},
extractor: values.NewExtractor(),
client: &http.Client{Timeout: httpExecutorRunTimeout},
extractor: save.NewExtractor(),
assertor: assert.NewRunner(),
passer: form.NewRunner(),
}
Expand Down Expand Up @@ -96,7 +98,6 @@ func (e *HTTPExecutor) Run(ctx interfaces.ExecutionContext, manifest manifests.M
func (e *HTTPExecutor) runCase(ctx interfaces.ExecutionContext, man *api.Http, c api.HttpCase) error {
output := ctx.GetOutput()

start := time.Now()
caseResult := &interfaces.CaseResult{
Name: c.Name,
Values: make(map[string]any),
Expand All @@ -114,33 +115,21 @@ func (e *HTTPExecutor) runCase(ctx interfaces.ExecutionContext, man *api.Http, c
defer func() {
metrics.CollectHTTPMetrics(req, resp, c.Details, caseResult)

caseResult.Duration = time.Since(start)
output.EndCase(man, c.Name, caseResult)
}()

url := c.Url

if url == "" {
baseUrl := strings.TrimRight(man.Spec.Target, "/")
endpoint := strings.TrimLeft(c.Endpoint, "/")

if baseUrl != "" && endpoint != "" {
url = baseUrl + "/" + endpoint
} else if baseUrl != "" {
url = baseUrl
} else {
url = endpoint
}
}
// Building url to testing target
url := buildHttpURL(c.Url, man.Spec.Target, c.Endpoint)

// Applying save from Pass declaration
url = e.passer.Apply(ctx, url, c.Pass)
headers := e.passer.MapHeaders(ctx, c.Headers, c.Pass)
body := e.passer.ApplyBody(ctx, c.Body, c.Pass)

reqBody := &bytes.Buffer{}

if body != nil {
if err := json.NewEncoder(reqBody).Encode(body); err != nil {
if err = json.NewEncoder(reqBody).Encode(body); err != nil {
caseResult.Errors = append(caseResult.Errors, fmt.Sprintf("failed to encode request body: %s", err.Error()))
return fmt.Errorf("encode body failed: %w", err)
}
Expand All @@ -156,13 +145,14 @@ func (e *HTTPExecutor) runCase(ctx interfaces.ExecutionContext, man *api.Http, c
req.Header.Set(k, v)
}

timeout := c.Timeout
if timeout == 0 {
timeout = 5 * time.Second
if c.Timeout > 0 {
e.client.Timeout = c.Timeout
}

client := &http.Client{Timeout: timeout}
resp, err = client.Do(req)
start := time.Now()
resp, err = e.client.Do(req)
caseResult.Duration = time.Since(start)

if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
caseResult.Errors = append(caseResult.Errors, "request timed out")
Expand All @@ -185,14 +175,13 @@ func (e *HTTPExecutor) runCase(ctx interfaces.ExecutionContext, man *api.Http, c
return fmt.Errorf("read response body failed: %w", err)
}

e.extractor.Extract(ctx, man, c.HttpCase, resp, reqBody.Bytes(), respBody, caseResult)
if c.Save != nil {
output.Logf(interfaces.InfoLevel, "%s data extraction for %s %s ", httpExecutorOutputPrefix, man.GetName(), man.Spec.Target)

e.extractor.Extract(ctx, man.GetID(), c.HttpCase, resp, respBody)
output.Logf(interfaces.InfoLevel, "%s data extraction for %s %s", httpExecutorOutputPrefix, man.GetName(), c.Name)
}

if c.Assert != nil {
output.Logf(interfaces.InfoLevel, "%s reponse asserting for %s %s ", httpExecutorOutputPrefix, man.GetName(), man.Spec.Target)
output.Logf(interfaces.InfoLevel, "%s reponse asserting for %s %s", httpExecutorOutputPrefix, man.GetName(), c.Name)

if err = e.assertor.Assert(ctx, c.Assert, resp, respBody); err != nil {
caseResult.Assert = "no"
Expand All @@ -209,3 +198,20 @@ func (e *HTTPExecutor) runCase(ctx interfaces.ExecutionContext, man *api.Http, c

return nil
}

func buildHttpURL(url, target, endpoint string) string {
if url == "" {
baseUrl := strings.TrimRight(target, "/")
ep := strings.TrimLeft(endpoint, "/")

if baseUrl != "" && ep != "" {
url = baseUrl + "/" + ep
} else if baseUrl != "" {
url = baseUrl
} else {
url = ep
}
}

return url
}
Loading
Loading