Skip to content

Commit 4238470

Browse files
authored
Merge pull request #4 from jaypipes/uplift
bring gdt-http up to modern gdt core
2 parents bb6f352 + e7983bc commit 4238470

20 files changed

Lines changed: 1068 additions & 484 deletions

.github/workflows/fmtcheck.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: fmtcheck
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
fmtcheck:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: harden runner
17+
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
18+
with:
19+
egress-policy: block
20+
disable-sudo: true
21+
allowed-endpoints: >
22+
github.com:443
23+
api.github.com:443
24+
proxy.github.com:443
25+
proxy.golang.org:443
26+
raw.githubusercontent.com:443
27+
objects.githubusercontent.com:443
28+
proxy.golang.org:443
29+
- name: checkout code
30+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
31+
- name: setup go
32+
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
33+
with:
34+
go-version: 1.21
35+
- name: check fmt
36+
run: 'bash -c "diff -u <(echo -n) <(gofmt -d .)"'

.github/workflows/gate-tests.yml

Lines changed: 0 additions & 34 deletions
This file was deleted.

.github/workflows/lint.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: lint
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
permissions:
10+
contents: read
11+
pull-requests: read # needed for only-new-issues option below
12+
13+
jobs:
14+
lint:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: harden runner
18+
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
19+
with:
20+
egress-policy: audit
21+
disable-sudo: true
22+
- name: checkout code
23+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
24+
- name: setup go
25+
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
26+
with:
27+
go-version: 1.24
28+
- name: lint
29+
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
30+
with:
31+
version: v2.2.0
32+
args: --timeout=5m0s --verbose
33+
only-new-issues: true

.github/workflows/test.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: gate tests
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test-mac-windows:
14+
strategy:
15+
matrix:
16+
go: ['1.23']
17+
os: [macos-latest, windows-latest]
18+
runs-on: ${{ matrix.os }}
19+
steps:
20+
- name: harden runner
21+
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
22+
with:
23+
egress-policy: block
24+
disable-sudo: true
25+
allowed-endpoints: >
26+
github.com:443
27+
api.github.com:443
28+
proxy.github.com:443
29+
raw.githubusercontent.com:443
30+
objects.githubusercontent.com:443
31+
proxy.golang.org:443
32+
blob.core.windows.net:443
33+
- name: checkout code
34+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
35+
- name: setup go
36+
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
37+
with:
38+
go-version: ${{ matrix.go }}
39+
check-latest: true
40+
- name: run tests
41+
run: make test
42+
test-linux:
43+
strategy:
44+
matrix:
45+
go: ['1.21', '1.22', '1.23']
46+
os: [ubuntu-latest]
47+
runs-on: ${{ matrix.os }}
48+
steps:
49+
- name: harden runner
50+
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
51+
with:
52+
egress-policy: block
53+
disable-sudo: true
54+
allowed-endpoints: >
55+
github.com:443
56+
api.github.com:443
57+
proxy.github.com:443
58+
raw.githubusercontent.com:443
59+
objects.githubusercontent.com:443
60+
proxy.golang.org:443
61+
blob.core.windows.net:443
62+
- name: checkout code
63+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
64+
- name: setup go
65+
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
66+
with:
67+
go-version: ${{ matrix.go }}
68+
check-latest: true
69+
- name: run tests
70+
run: make test

action.go

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// Use and distribution licensed under the Apache license version 2.
2+
//
3+
// See the COPYING file in the root project directory for full text.
4+
5+
package http
6+
7+
import (
8+
"bytes"
9+
"context"
10+
"encoding/json"
11+
"fmt"
12+
"io"
13+
nethttp "net/http"
14+
"reflect"
15+
"strings"
16+
17+
gdtcontext "github.com/gdt-dev/core/context"
18+
"github.com/gdt-dev/core/debug"
19+
)
20+
21+
// Action describes the the HTTP-specific action that is performed by the test.
22+
type Action struct {
23+
// URL being called by HTTP client. Used with the `Method` field.
24+
URL string `yaml:"url,omitempty"`
25+
// HTTP Method specified by HTTP client. Used with the `URL` shortcut field.
26+
Method string `yaml:"method,omitempty"`
27+
// Data is the payload to send along in request
28+
Data interface{} `yaml:"data,omitempty"`
29+
// Shortcut for URL and Method of "GET"
30+
Get string `yaml:"get,omitempty"`
31+
// Shortcut for URL and Method of "POST"
32+
Post string `yaml:"post,omitempty"`
33+
// Shortcut for URL and Method of "PUT"
34+
Put string `yaml:"put,omitempty"`
35+
// Shortcut for URL and Method of "PATCH"
36+
Patch string `yaml:"patch,omitempty"`
37+
// Shortcut for URL and Method of "DELETE"
38+
Delete string `yaml:"delete,omitempty"`
39+
}
40+
41+
// Do performs a single HTTP request, returning the HTTP Response and any
42+
// runtime error.
43+
func (a *Action) Do(
44+
ctx context.Context,
45+
c *nethttp.Client,
46+
defaults *Defaults,
47+
) (*nethttp.Response, error) {
48+
url, err := a.getURL(ctx, defaults)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
debug.Printf(ctx, "http: > %s %s", a.Method, url)
54+
var reqData io.Reader
55+
if a.Data != nil {
56+
if err := a.processRequestData(ctx); err != nil {
57+
return nil, err
58+
}
59+
jsonBody, err := json.Marshal(a.Data)
60+
if err != nil {
61+
return nil, err
62+
}
63+
b := bytes.NewReader(jsonBody)
64+
if b.Size() > 0 {
65+
sendData, _ := io.ReadAll(b)
66+
debug.Printf(ctx, "http: > %s", sendData)
67+
b.Seek(0, 0) // nolint:errcheck
68+
}
69+
reqData = b
70+
}
71+
72+
req, err := nethttp.NewRequest(a.Method, url, reqData)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
resp, err := c.Do(req)
78+
if err != nil {
79+
return nil, err
80+
}
81+
debug.Printf(ctx, "http: < %d", resp.StatusCode)
82+
return resp, err
83+
}
84+
85+
// getURL returns the URL to use for the test's HTTP request. The test's url
86+
// field is first queried to see if it is the special $LOCATION string. If it
87+
// is, then we return the previous HTTP response's Location header. Otherwise,
88+
// we construct the URL from the httpFile's base URL and the test's url field.
89+
func (a *Action) getURL(
90+
ctx context.Context,
91+
defaults *Defaults,
92+
) (string, error) {
93+
if strings.ToUpper(a.URL) == "$LOCATION" {
94+
pr := priorRunData(ctx)
95+
if pr == nil || pr.Response == nil {
96+
panic("test unit referenced $LOCATION before executing an HTTP request")
97+
}
98+
url, err := pr.Response.Location()
99+
if err != nil {
100+
return "", ErrExpectedLocationHeader
101+
}
102+
return url.String(), nil
103+
}
104+
base := defaults.BaseURLFromContext(ctx)
105+
return base + a.URL, nil
106+
}
107+
108+
// processRequestData looks through the raw data interface{} that was
109+
// unmarshaled during parse for any string values that look like JSONPath
110+
// expressions. If we find any, we query the fixture registry to see if any
111+
// fixtures have a value that matches the JSONPath expression. See
112+
// gdt.fixtures:jsonFixture for more information on how this works
113+
func (a *Action) processRequestData(ctx context.Context) error {
114+
if a.Data == nil {
115+
return nil
116+
}
117+
// Get a pointer to the unmarshaled interface{} so we can mutate the
118+
// contents pointed to
119+
p := reflect.ValueOf(&a.Data)
120+
121+
// We're interested in the value pointed to by the interface{}, which is
122+
// why we do a double Elem() here.
123+
v := p.Elem().Elem()
124+
vt := v.Type()
125+
126+
switch vt.Kind() {
127+
case reflect.Slice:
128+
for i := 0; i < v.Len(); i++ {
129+
item := v.Index(i).Elem()
130+
it := item.Type()
131+
err := a.preprocessMap(ctx, item, it.Key(), it.Elem())
132+
if err != nil {
133+
return err
134+
}
135+
}
136+
case reflect.Map:
137+
err := a.preprocessMap(ctx, v, vt.Key(), vt.Elem())
138+
if err != nil {
139+
return err
140+
}
141+
}
142+
return nil
143+
}
144+
145+
// processRequestDataMap processes a map pointed to by v, transforming any
146+
// string keys or values of the map into the results of calling the fixture
147+
// set's State() method.
148+
func (a *Action) preprocessMap(
149+
ctx context.Context,
150+
m reflect.Value,
151+
kt reflect.Type,
152+
vt reflect.Type,
153+
) error {
154+
it := m.MapRange()
155+
for it.Next() {
156+
if kt.Kind() == reflect.String {
157+
keyStr := it.Key().String()
158+
fixtures := gdtcontext.Fixtures(ctx)
159+
for _, f := range fixtures {
160+
if !f.HasState(keyStr) {
161+
continue
162+
}
163+
trKeyStr := f.State(keyStr)
164+
keyStr = trKeyStr.(string)
165+
}
166+
167+
val := it.Value()
168+
err := a.preprocessMapValue(ctx, m, reflect.ValueOf(keyStr), val, val.Type())
169+
if err != nil {
170+
return err
171+
}
172+
}
173+
}
174+
return nil
175+
}
176+
177+
func (a *Action) preprocessMapValue(
178+
ctx context.Context,
179+
m reflect.Value,
180+
k reflect.Value,
181+
v reflect.Value,
182+
vt reflect.Type,
183+
) error {
184+
if vt.Kind() == reflect.Interface {
185+
v = v.Elem()
186+
vt = v.Type()
187+
}
188+
189+
switch vt.Kind() {
190+
case reflect.Slice:
191+
for i := 0; i < v.Len(); i++ {
192+
item := v.Index(i)
193+
fmt.Println(item)
194+
}
195+
fmt.Printf("map element is an array.\n")
196+
case reflect.Map:
197+
return a.preprocessMap(ctx, v, vt.Key(), vt.Elem())
198+
case reflect.String:
199+
valStr := v.String()
200+
fixtures := gdtcontext.Fixtures(ctx)
201+
for _, f := range fixtures {
202+
if !f.HasState(valStr) {
203+
continue
204+
}
205+
trValStr := f.State(valStr)
206+
m.SetMapIndex(k, reflect.ValueOf(trValStr))
207+
}
208+
default:
209+
return nil
210+
}
211+
return nil
212+
}

0 commit comments

Comments
 (0)