Skip to content

Commit 368f61c

Browse files
authored
Merge pull request #12 from apiqube/dev
Dev
2 parents e7dd3fa + 77d6772 commit 368f61c

23 files changed

Lines changed: 942 additions & 50 deletions

File tree

cmd/cli/check/check.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@ var cmdPlanCheck = &cobra.Command{
5252
planManifest, err := extractPlanManifest(loadedManifests)
5353
if err != nil {
5454
cli.Errorf("Failed to check plan manifest: %v", err)
55+
return
5556
}
5657

5758
if err := validatePlan(planManifest); err != nil {
5859
cli.Errorf("Failed to check plan: %v", err)
60+
return
5961
}
6062

6163
cli.Successf("Successfully checked plan manifest")

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/pterm/pterm v0.12.80
1313
github.com/spf13/cobra v1.9.1
1414
github.com/spf13/viper v1.20.1
15+
github.com/tidwall/gjson v1.18.0
1516
gopkg.in/yaml.v3 v3.0.1
1617
)
1718

@@ -76,6 +77,8 @@ require (
7677
github.com/spf13/cast v1.7.1 // indirect
7778
github.com/spf13/pflag v1.0.6 // indirect
7879
github.com/subosito/gotenv v1.6.0 // indirect
80+
github.com/tidwall/match v1.1.1 // indirect
81+
github.com/tidwall/pretty v1.2.1 // indirect
7982
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
8083
go.etcd.io/bbolt v1.4.0 // indirect
8184
go.opentelemetry.io/auto/sdk v1.1.0 // indirect

go.sum

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,13 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
202202
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
203203
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
204204
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
205+
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
206+
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
207+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
208+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
209+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
210+
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
211+
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
205212
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
206213
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
207214
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=

internal/core/manifests/interface.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const (
1414

1515
const (
1616
PlanManifestKind = "Plan"
17-
ValuesManifestLind = "Values"
17+
ValuesManifestKind = "Values"
1818
ServerManifestKind = "Server"
1919
ServiceManifestKind = "Service"
2020
HttpTestManifestKind = "HttpTest"

internal/core/manifests/kinds/plan/plan.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55
"time"
66

7+
"github.com/apiqube/cli/internal/core/runner/hooks"
8+
79
"github.com/apiqube/cli/internal/core/manifests/utils"
810
"github.com/google/uuid"
911

@@ -35,19 +37,14 @@ type Stage struct {
3537
Parallel bool `yaml:"parallel,omitempty" json:"parallel,omitempty"`
3638
Params map[string]any `yaml:"params,omitempty" json:"params,omitempty" validate:"omitempty"`
3739
Mode string `yaml:"mode,omitempty" json:"mode,omitempty" validate:"omitempty,oneof=strict parallel"` // (strict|parallel)
38-
Hooks Hooks `yaml:"hooks,omitempty" json:"hooks,omitempty" validate:"omitempty,dive"`
40+
Hooks *Hooks `yaml:"hooks,omitempty" json:"hooks,omitempty" validate:"omitempty,dive"`
3941
}
4042

4143
type Hooks struct {
42-
BeforeStart []Action `yaml:"beforeStart,omitempty" json:"beforeStart,omitempty" validate:"omitempty,dive"`
43-
AfterFinish []Action `yaml:"afterFinish,omitempty" json:"afterFinish,omitempty" validate:"omitempty,dive"`
44-
OnSuccess []Action `yaml:"onSuccess,omitempty" json:"onSuccess,omitempty" validate:"omitempty,dive"`
45-
OnFailure []Action `yaml:"onFailure,omitempty" json:"onFailure,omitempty" validate:"omitempty,dive"`
46-
}
47-
48-
type Action struct {
49-
Type string `yaml:"type" json:"type" validate:"required,oneof=log save skip fail exec notify"` // eg log/save/skip/fail/exec/notify
50-
Params map[string]any `yaml:"params" json:"params" validate:"required"`
44+
BeforeRun []hooks.Action `yaml:"beforeRun,omitempty" json:"beforeRun,omitempty" validate:"omitempty,dive"`
45+
AfterRun []hooks.Action `yaml:"afterRun,omitempty" json:"afterRun,omitempty" validate:"omitempty,dive"`
46+
OnSuccess []hooks.Action `yaml:"onSuccess,omitempty" json:"onSuccess,omitempty" validate:"omitempty,dive"`
47+
OnFailure []hooks.Action `yaml:"onFailure,omitempty" json:"onFailure,omitempty" validate:"omitempty,dive"`
5148
}
5249

5350
func (p *Plan) GetID() string {

internal/core/manifests/kinds/servers/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ type Server struct {
1919
kinds.BaseManifest `yaml:",inline" json:",inline" validate:"required"`
2020

2121
Spec struct {
22-
BaseUrl string `yaml:"baseUrl" json:"baseUrl" validate:"required,url"`
22+
BaseURL string `yaml:"baseUrl" json:"baseUrl" validate:"required,url"`
23+
Health string `yaml:"health" json:"health" validate:"omitempty,max=100"`
2324
Headers map[string]string `yaml:"headers,omitempty" json:"headers"`
2425
} `yaml:"spec" json:"spec" validate:"required"`
2526

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package accessor
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
8+
"github.com/apiqube/cli/internal/core/runner/interfaces"
9+
)
10+
11+
type DataAccessor interface {
12+
Get(path string) (any, error)
13+
GetString(path string) (string, error)
14+
GetInt(path string) (int64, error)
15+
GetFloat(path string) (float64, error)
16+
GetBool(path string) (bool, error)
17+
GetStringSlice(path string) ([]string, error)
18+
GetMap(path string) (map[string]any, error)
19+
}
20+
21+
var _ DataAccessor = (*Accessor)(nil)
22+
23+
type Accessor struct {
24+
store interfaces.DataStore
25+
}
26+
27+
func NewAccessor(store interfaces.DataStore) *Accessor {
28+
return &Accessor{store: store}
29+
}
30+
31+
func (a *Accessor) Get(path string) (any, error) {
32+
key, subPath := splitKeyAndPath(path)
33+
root, ok := a.store.Get(key)
34+
if !ok {
35+
return nil, fmt.Errorf("key not found: %s", key)
36+
}
37+
return walkPath(root, subPath)
38+
}
39+
40+
func (a *Accessor) GetString(path string) (string, error) {
41+
v, err := a.Get(path)
42+
if err != nil {
43+
return "", err
44+
}
45+
46+
val, ok := v.(string)
47+
if !ok {
48+
return "", fmt.Errorf("not a string at path %s", path)
49+
}
50+
51+
return val, nil
52+
}
53+
54+
func (a *Accessor) GetInt(path string) (int64, error) {
55+
v, err := a.Get(path)
56+
if err != nil {
57+
return -1, err
58+
}
59+
60+
val, ok := v.(int64)
61+
if !ok {
62+
return -1, fmt.Errorf("not a int at path %s", path)
63+
}
64+
65+
return val, nil
66+
}
67+
68+
func (a *Accessor) GetFloat(path string) (float64, error) {
69+
v, err := a.Get(path)
70+
if err != nil {
71+
return -1, err
72+
}
73+
74+
val, ok := v.(float64)
75+
if !ok {
76+
return -1, fmt.Errorf("not a float at path %s", path)
77+
}
78+
79+
return val, nil
80+
}
81+
82+
func (a *Accessor) GetBool(path string) (bool, error) {
83+
v, err := a.Get(path)
84+
if err != nil {
85+
return false, err
86+
}
87+
88+
val, ok := v.(bool)
89+
if !ok {
90+
return false, fmt.Errorf("not a bool at path %s", path)
91+
}
92+
93+
return val, nil
94+
}
95+
96+
func (a *Accessor) GetStringSlice(path string) ([]string, error) {
97+
v, err := a.Get(path)
98+
if err != nil {
99+
return []string{}, err
100+
}
101+
102+
val, ok := v.([]string)
103+
if !ok {
104+
return []string{}, fmt.Errorf("not a string slice at path %s", path)
105+
}
106+
107+
return val, nil
108+
}
109+
110+
func (a *Accessor) GetMap(path string) (map[string]any, error) {
111+
v, err := a.Get(path)
112+
if err != nil {
113+
return map[string]any{}, err
114+
}
115+
116+
val, ok := v.(map[string]any)
117+
if !ok {
118+
return map[string]any{}, fmt.Errorf("not a map at path %s", path)
119+
}
120+
121+
return val, nil
122+
}
123+
124+
func splitKeyAndPath(full string) (string, string) {
125+
if idx := strings.LastIndex(full, "."); idx != -1 {
126+
return full[:idx], full[idx+1:]
127+
}
128+
129+
return full, ""
130+
}
131+
132+
func walkPath(v any, path string) (any, error) {
133+
if path == "" {
134+
return v, nil
135+
}
136+
137+
parts := strings.Split(path, ".")
138+
cur := v
139+
140+
for _, part := range parts {
141+
switch val := cur.(type) {
142+
case map[string]any:
143+
cur = val[part]
144+
case []any:
145+
idx, err := strconv.Atoi(part)
146+
if err != nil || idx >= len(val) {
147+
return nil, fmt.Errorf("invalid index: %s", part)
148+
}
149+
cur = val[idx]
150+
default:
151+
return nil, fmt.Errorf("unsupported or unexpected type at %s", part)
152+
}
153+
}
154+
return cur, nil
155+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package assert
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"reflect"
7+
"strings"
8+
9+
"github.com/apiqube/cli/internal/core/manifests/kinds/tests"
10+
"github.com/apiqube/cli/internal/core/runner/interfaces"
11+
"github.com/tidwall/gjson"
12+
)
13+
14+
type Runner struct{}
15+
16+
func NewRunner() *Runner {
17+
return &Runner{}
18+
}
19+
20+
func (a *Runner) Assert(_ interfaces.ExecutionContext, assert *tests.Assert, _ *http.Response, raw []byte, _ any) error {
21+
for _, el := range assert.Assertions {
22+
val := gjson.GetBytes(raw, el.Target).Value()
23+
24+
if el.Equals != nil && !reflect.DeepEqual(val, el.Equals) {
25+
return fmt.Errorf("expected %v to equal %v", val, el.Equals)
26+
}
27+
if el.Contains != "" {
28+
if s, ok := val.(string); !ok || !strings.Contains(s, el.Contains) {
29+
return fmt.Errorf("expected %v to contain %q", val, el.Contains)
30+
}
31+
}
32+
if el.Exists && val == nil {
33+
return fmt.Errorf("expected %v to exist", el.Target)
34+
}
35+
}
36+
return nil
37+
}

internal/core/runner/cli/output.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,13 @@ func (o *Output) StartCase(manifest manifests.Manifest, caseName string) {
2424

2525
func (o *Output) EndCase(manifest manifests.Manifest, caseName string, result *interfaces.CaseResult) {
2626
if result != nil {
27-
cli.Println(fmt.Sprintf(
28-
`Finish %s case from %s manifest with next reults
29-
Result: %s
30-
Success: %v
31-
Status Code: %d
32-
Duration: %s`,
27+
cli.Infof("Finish %s case from %s manifest with next reults\nResult: %s\nSuccess: %v\nStatus Code: %d\nDuration: %s",
3328
caseName,
3429
manifest.GetName(),
3530
result.Name,
3631
result.Success,
3732
result.StatusCode,
3833
result.Duration.String(),
39-
),
4034
)
4135
} else {
4236
cli.Infof("Finish %s case from %s manifest", caseName, manifest.GetName())

internal/core/runner/context/builder.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"reflect"
66
"sync"
77

8+
"github.com/apiqube/cli/internal/core/runner/cli"
9+
810
"github.com/apiqube/cli/internal/core/manifests"
911
"github.com/apiqube/cli/internal/core/runner/interfaces"
1012
)
@@ -35,6 +37,7 @@ func NewCtxBuilder() *CtxBuilder {
3537
passChans: make(map[string]chan any),
3638
passKinds: make(map[string]reflect.Kind),
3739
passDone: make(map[string]bool),
40+
output: cli.NewOutput(),
3841
}
3942
}
4043

0 commit comments

Comments
 (0)