Skip to content
Merged

Dev #16

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
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ jobs:
gofumpt -l -w .
git diff --exit-code

- name: golangci-lint
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
skip-pkg-cache: true
args: --issues-exit-code=0

- name: Run Units Tests
run: |
go test -v -coverpkg=./... ./...

release:
needs: build-and-test
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion .golanci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ run:
- "gen"
- "tests"
skip-files:
- ".*\\.pb\\.go
- ".*\\.pb\\.go
- ".*\\_test.go"
5 changes: 5 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ tasks:
cmds:
- reflex -r '\.go$$' -s -- sh -c "task build && task run"

test:
desc: Run all tests
cmds:
- go test -v -coverpkg=./... -coverprofile=cover.out ./...

fmt:
desc: 🧹 Cleaning all go code
cmds:
Expand Down
51 changes: 28 additions & 23 deletions cmd/cli/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,35 +32,40 @@ var Cmd = &cobra.Command{
return
}

cli.Infof("Loading manifests from: %s", file)
loadedMans, cachedMans, err := io.LoadManifests(file)
if err != nil {
cli.Errorf("Critical load error:\n%s", formatLoadError(err, file))
return
}
_ = applyManifests(file)
},
}

cli.Info("Validating manifests...")
validator := validate.NewManifestValidator(validate.NewValidator(), cli.Instance())
func applyManifests(filePath string) error {
cli.Infof("Loading manifests from: %s", filePath)
loadedMans, cachedMans, err := io.LoadManifests(filePath)
if err != nil {
cli.Errorf("Critical load error:\n%s", formatLoadError(err, filePath))
return err
}

validator.Validate(loadedMans...)
printManifestsLoadResult(loadedMans, cachedMans)

validMans := validator.Valid()
if len(validMans) == 0 {
cli.Warning("No valid manifests to apply")
return
}
cli.Info("Validating manifests...")
validator := validate.NewManifestValidator(validate.NewValidator(), cli.Instance())

printManifestsLoadResult(validMans, cachedMans)
validator.Validate(loadedMans...)

cli.Infof("Saving %d manifests to storage...", len(validMans))
if err := store.Save(validMans...); err != nil {
cli.Errorf("Storage error: -\n%s", err.Error())
return
}
validMans := validator.Valid()
if len(validMans) == 0 {
cli.Info("No new valid manifests to apply")
return errors.New("no new valid manifests to apply")
}

printPostApplySummary(validMans)
cli.Successf("Successfully applied %d manifests", len(validMans))
},
cli.Infof("Saving %d manifests to storage...", len(validMans))
if err = store.Save(validMans...); err != nil {
cli.Errorf("Storage error: -\n%s", err.Error())
return err
}

printPostApplySummary(validMans)
cli.Successf("Successfully applied %d manifests", len(validMans))
return nil
}

func formatLoadError(err error, file string) string {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func loadManifests(opts *checkPlanOptions) ([]manifests.Manifest, error) {
}

func extractPlanManifest(mans []manifests.Manifest) (*plan.Plan, error) {
man, err := findManifestWithKind(manifests.PlanManifestKind, mans)
man, err := findManifestWithKind(manifests.PlanKind, mans)
if err != nil {
return nil, err
}
Expand Down
15 changes: 15 additions & 0 deletions cmd/cli/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"strings"

"github.com/apiqube/cli/internal/validate"

"github.com/apiqube/cli/internal/core/io"
"github.com/apiqube/cli/internal/core/manifests"
"github.com/apiqube/cli/internal/core/runner/context"
Expand Down Expand Up @@ -35,6 +37,19 @@ var Cmd = &cobra.Command{
}

cli.Infof("Loaded %d manifests", len(loadedManifests))

cli.Info("Validating manifests...")
validator := validate.NewManifestValidator(validate.NewValidator(), cli.Instance())

validator.Validate(loadedManifests...)

validMans := validator.Valid()
if len(validMans) == 0 {
cli.Warning("No valid manifests to run tests")
return
}

cli.Success("All manifests valid")
cli.Info("Generating plan...")

manager := runner.NewPlanManagerBuilder().
Expand Down
38 changes: 16 additions & 22 deletions examples/simple-http-tests/http_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ kind: HttpTest
metadata:
# name: Unique identifier for this test suite
name: simple-test-example

# namespace: Logical grouping for organizational purposes
namespace: simple-http-tests

Expand All @@ -22,26 +22,20 @@ spec:
# cases: List of test scenarios to execute
cases:
# Test Case 1: Basic GET request validation
- name: Get All Users Test # Descriptive test name
method: GET # HTTP method (GET/POST/PUT/etc)
endpoint: /users # Appended to target URL

# assert: Validation rules
assert:
- target: status # What to validate (status code)
equals: 200 # Expected value (HTTP 200 OK)
- name: Get All Users Test # Descriptive test name
method: GET # HTTP method (GET/POST/PUT/etc)
endpoint: /users # Appended to target URL
assert: # Assert: Validation rules
- target: status # What to validate (status code)
equals: 200 # Expected value (HTTP 200 OK)

# Test Case 2: POST request with payload
- name: Create New User With Body
method: POST
endpoint: /users

# headers: Request headers to include
headers:
Content-Type: application/json # Specifies JSON payload

# body: Request payload (automatically JSON-encoded)
body:
headers: # headers: Request headers to include
Content-Type: application/json # Specifies JSON payload
body: # body: Request payload (automatically JSON-encoded)
name: "{{ Fake.name }}" # Generate fake name for request
email: "{{ Fake.email }}" # Generate fake email for request
age: "{{ Fake.uint.10.100 }}" # Generate fake positive number between 10 and 100 including
Expand All @@ -50,26 +44,26 @@ spec:
number: "{{ Fake.name }}"
assert:
- target: status
equals: 201 # HTTP 201 Created
equals: 201 # HTTP 201 Created

# Test Case 3: Getting and validating a user
- name: Get User By ID Test
method: GET
endpoint: /users/1 # Endpoint with user ID
endpoint: /users/1 # Endpoint with user ID
assert:
- target: status
equals: 200

# Test Case 4: Absolute URL test
- name: Always Fail Endpoint Test
method: GET
url: http://127.0.0.1:8081/fail # Overrides spec.target
url: http://127.0.0.1:8081/fail # Overrides spec.target
assert:
- target: status
equals: 500 # Expecting server error
equals: 500 # Expecting server error

# Test Case 5: Performance testing
- name: Slow Endpoint Response Test
method: GET
endpoint: /slow?delay=2s # Test endpoint with artificial delay
timeout: 3s # Fail if response > 3 seconds
endpoint: /slow?delay=2s # Test endpoint with artificial delay
timeout: 3s # Fail if response > 3 seconds
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/pterm/pterm v0.12.80
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.18.0
gopkg.in/yaml.v3 v3.0.1
)
Expand Down Expand Up @@ -47,6 +48,7 @@ require (
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
Expand All @@ -71,6 +73,7 @@ require (
github.com/mschoch/smat v0.2.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions internal/core/io/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func processFile(filePath string, manifestsSet map[string]struct{}) (new []manif
if existingManifest != nil {
if _, exists := manifestsSet[existingManifest.GetID()]; !exists {
manifestsSet[existingManifest.GetID()] = struct{}{}
cachedManifests = append(cachedManifests, existingManifest)
}
continue
}
Expand Down
26 changes: 13 additions & 13 deletions internal/core/manifests/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ const (
)

const (
V1Version = "v1"
V1 = "v1"
)

const (
PlanManifestKind = "Plan"
ValuesManifestKind = "Values"
ServerManifestKind = "Server"
ServiceManifestKind = "Service"
HttpTestManifestKind = "HttpTest"
HttpLoadTestManifestKind = "HttpLoadTest"
GRPCTestManifestKind = "GRPCTest"
GRPCLoadTestManifestKind = "GRPCLoadTest"
WSTestManifestKind = "WSTest"
WSLoadTestManifestKind = "WSLoadTest"
GRAPHQLTestManifestKind = "GraphQLTest"
GRAPHQLLoadTestManifestKind = "GraphQLLoadTest"
PlanKind = "Plan"
ValuesKind = "Values"
ServerKind = "Server"
ServiceKind = "Service"
HttpTestKind = "HttpTest"
HttpLoadTestKind = "HttpLoadTest"
GRPCTestKind = "GRPCTest"
GRPCLoadTestKind = "GRPCLoadTest"
WSTestKind = "WSTest"
WSLoadTestKind = "WSLoadTest"
GRAPHQLTestKind = "GraphQLTest"
GRAPHQLLoadTestKind = "GraphQLLoadTest"
)

type Manifest interface {
Expand Down
12 changes: 6 additions & 6 deletions internal/core/manifests/kinds/plan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Plan struct {

Spec struct {
Stages []Stage `yaml:"stages" json:"stages" validate:"required,min=1,dive"`
Hooks *Hooks `yaml:"hooks,omitempty" json:"hooks,omitempty" validate:"omitempty,dive"`
Hooks *Hooks `yaml:"hooks,omitempty" json:"hooks,omitempty" validate:"omitempty"`
} `yaml:"spec" json:"spec" validate:"required"`

Meta *kinds.Meta `yaml:"-" json:"meta"`
Expand All @@ -37,7 +37,7 @@ type Stage struct {
Parallel bool `yaml:"parallel,omitempty" json:"parallel,omitempty"`
Params map[string]any `yaml:"params,omitempty" json:"params,omitempty" validate:"omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty" validate:"omitempty,oneof=strict parallel"` // (strict|parallel)
Hooks *Hooks `yaml:"hooks,omitempty" json:"hooks,omitempty" validate:"omitempty,dive"`
Hooks *Hooks `yaml:"hooks,omitempty" json:"hooks,omitempty" validate:"omitempty"`
}

type Hooks struct {
Expand Down Expand Up @@ -88,16 +88,16 @@ func (p *Plan) GetMeta() manifests.Meta {
}

func (p *Plan) Default() {
if p.Version != manifests.V1Version {
p.Version = manifests.V1Version
if p.Version != manifests.V1 {
p.Version = manifests.V1
}

if p.Name == "" {
p.Name = fmt.Sprintf("%s-%s", "generated", uuid.NewString()[:8])
}

if p.Kind == "" {
p.Kind = manifests.PlanManifestKind
p.Kind = manifests.PlanKind
}

if p.Namespace == "" {
Expand All @@ -115,7 +115,7 @@ func (p *Plan) Prepare() {
}

if p.Kind == "" {
p.Kind = manifests.PlanManifestKind
p.Kind = manifests.PlanKind
}

if p.Meta == nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/core/manifests/kinds/servers/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type Server struct {
Spec struct {
BaseURL string `yaml:"baseUrl" json:"baseUrl" validate:"required,url"`
Health string `yaml:"health" json:"health" validate:"omitempty,max=100"`
Headers map[string]string `yaml:"headers,omitempty" json:"headers"`
Headers map[string]string `yaml:"headers,omitempty" json:"headers" validate:"omitempty,max=20"`
} `yaml:"spec" json:"spec" validate:"required"`

Meta *kinds.Meta `yaml:"-" json:"meta"`
Expand Down
10 changes: 5 additions & 5 deletions internal/core/manifests/kinds/tests/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ type HttpCase struct {

type Assert struct {
Target string `yaml:"target,omitempty" json:"target,omitempty" validate:"required,oneof=status body headers"`
Equals any `yaml:"equals,omitempty" json:"equals,omitempty" validate:"omitempty,min=1,max=100"`
Contains string `yaml:"contains,omitempty" json:"contains,omitempty" validate:"omitempty,min=1,max=100"`
Equals any `yaml:"equals,omitempty" json:"equals,omitempty" validate:"omitempty"`
Contains string `yaml:"contains,omitempty" json:"contains,omitempty" validate:"omitempty,min=1"`
Exists bool `yaml:"exists,omitempty" json:"exists,omitempty" validate:"omitempty,boolean"`
Template string `yaml:"template,omitempty" json:"template,omitempty" validate:"omitempty,min=1,max=100,contains_template"`
Template string `yaml:"template,omitempty" json:"template,omitempty" validate:"omitempty,min=1,contains_template"`
}

type Save struct {
Json map[string]string `yaml:"json,omitempty" json:"json,omitempty" validate:"omitempty,dive,keys,endkeys,json"`
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"`
Expand All @@ -38,5 +38,5 @@ type Save struct {

type Pass struct {
From string `yaml:"from" json:"from" validate:"required,min=1,max=100"`
Map map[string]string `yaml:"map,omitempty" json:"map,omitempty" validate:"omitempty,min=1,max=100,keys,endkeys"`
Map map[string]string `yaml:"map,omitempty" json:"map,omitempty" validate:"omitempty,min=1,max=100,dive,keys,endkeys"`
}
2 changes: 1 addition & 1 deletion internal/core/manifests/kinds/tests/load/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type HttpCase struct {
Wave *WaveConfig `yaml:"wave,omitempty" json:"wave,omitempty" validate:"omitempty"`
Step *StepConfig `yaml:"step,omitempty" json:"step,omitempty" validate:"omitempty"`
Duration time.Duration `yaml:"duration,omitempty" json:"duration,omitempty" validate:"omitempty,duration"`
SaveEvery int `yaml:"saveEvery,omitempty" json:"saveEvery,omitempty" validate:"omitempty,min=1,max=1000"`
SaveEntry int `yaml:"saveEntry,omitempty" json:"saveEntry,omitempty" validate:"omitempty,min=1,max=1000"` // Int value or precent of saving
}

type WaveConfig struct {
Expand Down
Loading
Loading