Skip to content

Commit a7d9369

Browse files
authored
Merge pull request #31 from glothriel/integration-tests-go
Integration tests are now in go
2 parents a2ff9df + b73a02b commit a7d9369

File tree

9 files changed

+632
-948
lines changed

9 files changed

+632
-948
lines changed

.github/workflows/k6.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ jobs:
3939
run: ./k6 run tests/k6/test.js --vus 10 --duration 30s
4040

4141
- name: Archive k6 test result
42-
uses: actions/upload-artifact@v3
42+
uses: actions/upload-artifact@v4
4343
with:
4444
name: k6
4545
path: k6.html
4646

4747
- name: Archive system resources diagrams
48-
uses: actions/upload-artifact@v3
48+
uses: actions/upload-artifact@v4
4949
with:
5050
name: system
5151
path: system.html

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,10 @@ Other gunicorn flag variants had even lower performance.
6565
* SliceModelFields do not work with JSONSchemaValidator
6666
* Add support for model relations
6767
* ~~Add support for viewsets~~
68-
* Add support for authentication
6968
* Add support for complex pagination implementations
7069
* Add support for permissions (authorization)
7170
* Add support for automatic OpenAPI spec generation
72-
* Add support for caches
73-
* Add support for throttling
71+
7472
* Add support for output formatters
7573
* Add support for non-JSON types
7674
* Add support for translations (error messages)

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.21
44

55
require (
66
github.com/gin-gonic/gin v1.9.1
7+
github.com/go-playground/assert/v2 v2.2.0
78
github.com/go-playground/validator/v10 v10.14.1
89
github.com/google/uuid v1.3.0
910
github.com/mitchellh/mapstructure v1.5.0

pkg/detectors/internal.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding"
66
"fmt"
77
"reflect"
8+
"time"
89

910
"github.com/gin-gonic/gin"
1011
"github.com/glothriel/grf/pkg/fields"
@@ -48,6 +49,28 @@ func (p *usingGRFParsableToInternalValueDetector[Model]) ToInternalValue(fieldNa
4849
return nil, fmt.Errorf("Field `%s` is not a GRFParsable", fieldName)
4950
}
5051

52+
type isoTimeTimeToInternalValueDetector[Model any] struct{}
53+
54+
func (p *isoTimeTimeToInternalValueDetector[Model]) ToInternalValue(fieldName string) (fields.InternalValueFunc, error) {
55+
fieldSettings := getFieldSettings[Model](fieldName)
56+
if fieldSettings.itsType.Name() == "Time" && fieldSettings.itsType.PkgPath() == "time" {
57+
return ConvertFuncToInternalValueFuncAdapter(
58+
func(v any) (any, error) {
59+
vStr, isString := v.(string)
60+
if isString {
61+
t, err := time.Parse(time.RFC3339, vStr)
62+
if err != nil {
63+
return nil, err
64+
}
65+
return t, nil
66+
}
67+
return nil, fmt.Errorf("Field `%s` is not a string", fieldName)
68+
},
69+
), nil
70+
}
71+
return nil, fmt.Errorf("Field `%s` is not a time.Time", fieldName)
72+
}
73+
5174
type fromTypeMapperToInternalValueDetector[Model any] struct {
5275
mapper *types.FieldTypeMapper
5376
modelTypeNames map[string]string
@@ -144,6 +167,7 @@ func DefaultToInternalValueDetector[Model any]() ToInternalValueDetector {
144167
internalChild: &chainingToInternalValueDetector[Model]{
145168
children: []ToInternalValueDetector{
146169
&usingGRFParsableToInternalValueDetector[Model]{},
170+
&isoTimeTimeToInternalValueDetector[Model]{},
147171
&fromTypeMapperToInternalValueDetector[Model]{
148172
mapper: types.Mapper(),
149173
modelTypeNames: FieldTypes[Model](),

pkg/examples/alltypes/main.go

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

pkg/examples/products/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type Product struct {
3232

3333
Name string `json:"name" gorm:"size:191;column:name"`
3434
Description string `json:"description" gorm:"type:text;column:description"`
35-
Price decimal.Decimal `json:"price" gorm:"type:decimal(19,4)"`
35+
Price decimal.Decimal `json:"price" gorm:"type:decimal(8,2)"`
3636

3737
CategoryID string `json:"category_id" gorm:"size:191;column:category_id"`
3838
Category Category `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null;"`

pkg/integration/integration.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package integration
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/http/httptest"
10+
"testing"
11+
12+
"github.com/gin-gonic/gin"
13+
"github.com/glothriel/grf/pkg/queries"
14+
"github.com/glothriel/grf/pkg/views"
15+
"github.com/sirupsen/logrus"
16+
"github.com/stretchr/testify/require"
17+
"gorm.io/driver/sqlite"
18+
"gorm.io/gorm"
19+
)
20+
21+
func registerModel[Model any](
22+
prefix string,
23+
) *gin.Engine {
24+
gin.SetMode(gin.ReleaseMode)
25+
router := gin.New()
26+
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
27+
if err != nil {
28+
panic("failed to connect database")
29+
}
30+
views.NewModelViewSet[Model](prefix, queries.GORM[Model](gormDB).WithOrderBy(fmt.Sprintf("%s ASC", "created_at"))).Register(router)
31+
32+
var entity Model
33+
if migrateErr := gormDB.AutoMigrate(&entity); migrateErr != nil {
34+
logrus.Fatalf("Error migrating database: %s", migrateErr)
35+
}
36+
return router
37+
}
38+
39+
func NewRequest(method, url string, body map[string]any) *http.Request {
40+
req, _ := http.NewRequest(method, url, nil)
41+
if body != nil {
42+
req.Header.Set("Content-Type", "application/json")
43+
marshalledBody, marshalErr := json.Marshal(body)
44+
if marshalErr != nil {
45+
panic("failed to marshal body")
46+
}
47+
req.Body = io.NopCloser(bytes.NewReader(marshalledBody))
48+
}
49+
return req
50+
}
51+
52+
type TestCase struct {
53+
name string
54+
t *testing.T
55+
req *http.Request
56+
expectedCode int
57+
expectedJSON any
58+
}
59+
60+
func (tc *TestCase) Req(r *http.Request) *TestCase {
61+
tc.req = r
62+
return tc
63+
}
64+
65+
func (tc *TestCase) ExCode(c int) *TestCase {
66+
tc.expectedCode = c
67+
return tc
68+
}
69+
70+
func (tc *TestCase) ExJson(j any) *TestCase {
71+
tc.expectedJSON = j
72+
return tc
73+
}
74+
75+
func stripFields(v any, fields []string) any {
76+
vMap, ok := v.(map[string]any)
77+
if ok {
78+
for _, field := range fields {
79+
delete(vMap, field)
80+
}
81+
return vMap
82+
}
83+
vSlice, ok := v.([]any)
84+
if ok {
85+
for i, item := range vSlice {
86+
vSlice[i] = stripFields(item, fields)
87+
}
88+
return vSlice
89+
}
90+
return v
91+
}
92+
93+
func (tc *TestCase) Run(router *gin.Engine) string {
94+
var theID string
95+
w := httptest.NewRecorder()
96+
router.ServeHTTP(w, tc.req)
97+
98+
tc.t.Run(tc.name, func(t *testing.T) {
99+
require.Equal(t, tc.expectedCode, w.Code)
100+
if tc.expectedJSON != nil {
101+
var responseJSON any
102+
json.Unmarshal(w.Body.Bytes(), &responseJSON)
103+
rAsMap, ok := responseJSON.(map[string]any)
104+
if ok {
105+
id, ok := rAsMap["id"]
106+
if ok {
107+
theID = id.(string)
108+
}
109+
}
110+
require.Equal(t, tc.expectedJSON, stripFields(responseJSON, []string{"id", "created_at", "updated_at"}))
111+
112+
}
113+
})
114+
115+
return theID
116+
}
117+
118+
func NewAssertedReq(t *testing.T, name string) *TestCase {
119+
return &TestCase{
120+
t: t,
121+
name: name,
122+
}
123+
}

0 commit comments

Comments
 (0)