mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-06-28 15:57:37 +00:00
210 lines
6.3 KiB
Go
210 lines
6.3 KiB
Go
package restheadspec
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/bitechdev/ResolveSpec/pkg/common"
|
|
)
|
|
|
|
// detailTestModel is a simple model with gorm column/type tags for detail format tests.
|
|
type detailTestModel struct {
|
|
ID int64 `bun:"rid,pk" gorm:"column:rid;primaryKey" json:"rid"`
|
|
Name string `bun:"name" gorm:"column:name;type:citext" json:"name"`
|
|
Description *string `bun:"description" gorm:"column:description;type:text;nullable" json:"description"`
|
|
Score float64 `bun:"score" gorm:"column:score;type:numeric" json:"score"`
|
|
Active bool `bun:"active" gorm:"column:active;type:boolean;not null" json:"active"`
|
|
}
|
|
|
|
func TestSendFormattedResponse_DetailFormat(t *testing.T) {
|
|
handler := &Handler{}
|
|
|
|
name := "hello"
|
|
items := []*detailTestModel{
|
|
{ID: 1, Name: "first", Description: &name, Score: 1.5, Active: true},
|
|
{ID: 2, Name: "second", Description: nil, Score: 2.0, Active: false},
|
|
}
|
|
metadata := &common.Metadata{
|
|
Total: 36,
|
|
Count: 2,
|
|
Filtered: 36,
|
|
Limit: 10,
|
|
Offset: 0,
|
|
}
|
|
options := ExtendedRequestOptions{
|
|
ResponseFormat: "detail",
|
|
}
|
|
|
|
mockWriter := &MockTestResponseWriter{headers: make(map[string]string)}
|
|
handler.sendFormattedResponse(mockWriter, items, metadata, "myschema.myentity", detailTestModel{}, options)
|
|
|
|
if mockWriter.statusCode != 200 {
|
|
t.Fatalf("expected status 200, got %d", mockWriter.statusCode)
|
|
}
|
|
|
|
body, err := json.Marshal(mockWriter.body)
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal body: %v", err)
|
|
}
|
|
|
|
var resp map[string]json.RawMessage
|
|
if err := json.Unmarshal(body, &resp); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
t.Run("top-level keys", func(t *testing.T) {
|
|
for _, key := range []string{"count", "fields", "items", "tablename", "tableprefix", "total"} {
|
|
if _, ok := resp[key]; !ok {
|
|
t.Errorf("missing key %q in detail response", key)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("count and total are string", func(t *testing.T) {
|
|
var count, total string
|
|
if err := json.Unmarshal(resp["count"], &count); err != nil {
|
|
t.Errorf("count is not a string: %v", err)
|
|
}
|
|
if err := json.Unmarshal(resp["total"], &total); err != nil {
|
|
t.Errorf("total is not a string: %v", err)
|
|
}
|
|
if count != "2" {
|
|
t.Errorf("expected count %q, got %q", "2", count)
|
|
}
|
|
if total != "36" {
|
|
t.Errorf("expected total %q, got %q", "36", total)
|
|
}
|
|
})
|
|
|
|
t.Run("tablename and tableprefix", func(t *testing.T) {
|
|
var tablename, tableprefix string
|
|
json.Unmarshal(resp["tablename"], &tablename)
|
|
json.Unmarshal(resp["tableprefix"], &tableprefix)
|
|
if tablename != "myschema.myentity" {
|
|
t.Errorf("expected tablename %q, got %q", "myschema.myentity", tablename)
|
|
}
|
|
if tableprefix != "myentity" {
|
|
t.Errorf("expected tableprefix %q, got %q", "myentity", tableprefix)
|
|
}
|
|
})
|
|
|
|
t.Run("items contains data", func(t *testing.T) {
|
|
var itemSlice []map[string]interface{}
|
|
if err := json.Unmarshal(resp["items"], &itemSlice); err != nil {
|
|
t.Fatalf("items is not an array: %v", err)
|
|
}
|
|
if len(itemSlice) != 2 {
|
|
t.Errorf("expected 2 items, got %d", len(itemSlice))
|
|
}
|
|
})
|
|
|
|
t.Run("fields contains column metadata", func(t *testing.T) {
|
|
var fields []map[string]interface{}
|
|
if err := json.Unmarshal(resp["fields"], &fields); err != nil {
|
|
t.Fatalf("fields is not an array: %v", err)
|
|
}
|
|
if len(fields) == 0 {
|
|
t.Fatal("expected fields to be non-empty")
|
|
}
|
|
|
|
bySQL := make(map[string]map[string]interface{}, len(fields))
|
|
for _, f := range fields {
|
|
if sqlname, ok := f["sqlname"].(string); ok {
|
|
bySQL[sqlname] = f
|
|
}
|
|
}
|
|
|
|
// Check required field keys are present
|
|
for _, f := range fields {
|
|
for _, key := range []string{"name", "datatype", "sqlname", "sqldatatype", "sqlkey", "nullable"} {
|
|
if _, ok := f[key]; !ok {
|
|
t.Errorf("field %v missing key %q", f, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate specific columns
|
|
if col, ok := bySQL["rid"]; ok {
|
|
if col["sqlkey"] != "primary_key" {
|
|
t.Errorf("rid: expected sqlkey %q, got %v", "primary_key", col["sqlkey"])
|
|
}
|
|
} else {
|
|
t.Error("expected column 'rid' in fields")
|
|
}
|
|
|
|
if col, ok := bySQL["name"]; ok {
|
|
if col["sqldatatype"] != "citext" {
|
|
t.Errorf("name: expected sqldatatype %q, got %v", "citext", col["sqldatatype"])
|
|
}
|
|
if col["nullable"] != false {
|
|
t.Errorf("name: expected nullable false, got %v", col["nullable"])
|
|
}
|
|
} else {
|
|
t.Error("expected column 'name' in fields")
|
|
}
|
|
|
|
if col, ok := bySQL["description"]; ok {
|
|
if col["sqldatatype"] != "text" {
|
|
t.Errorf("description: expected sqldatatype %q, got %v", "text", col["sqldatatype"])
|
|
}
|
|
if col["nullable"] != true {
|
|
t.Errorf("description: expected nullable true, got %v", col["nullable"])
|
|
}
|
|
} else {
|
|
t.Error("expected column 'description' in fields")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSendFormattedResponse_DetailFormat_EmptyItems(t *testing.T) {
|
|
handler := &Handler{}
|
|
metadata := &common.Metadata{Total: 0, Count: 0, Filtered: 0}
|
|
options := ExtendedRequestOptions{ResponseFormat: "detail"}
|
|
|
|
mockWriter := &MockTestResponseWriter{headers: make(map[string]string)}
|
|
handler.sendFormattedResponse(mockWriter, []*detailTestModel{}, metadata, "s.t", detailTestModel{}, options)
|
|
|
|
body, _ := json.Marshal(mockWriter.body)
|
|
var resp map[string]json.RawMessage
|
|
json.Unmarshal(body, &resp)
|
|
|
|
var count, total string
|
|
json.Unmarshal(resp["count"], &count)
|
|
json.Unmarshal(resp["total"], &total)
|
|
|
|
if count != "0" || total != "0" {
|
|
t.Errorf("expected count/total both %q, got count=%q total=%q", "0", count, total)
|
|
}
|
|
|
|
var fields []interface{}
|
|
json.Unmarshal(resp["fields"], &fields)
|
|
if len(fields) == 0 {
|
|
t.Error("fields should still list column metadata even when items is empty")
|
|
}
|
|
}
|
|
|
|
func TestBuildDetailFields_SkipsRelations(t *testing.T) {
|
|
type child struct {
|
|
ID int64 `bun:"id,pk" gorm:"column:id;primaryKey" json:"id"`
|
|
}
|
|
type parent struct {
|
|
ID int64 `bun:"id,pk" gorm:"column:id;primaryKey" json:"id"`
|
|
Name string `bun:"name" gorm:"column:name" json:"name"`
|
|
Children []child `bun:"rel:has-many" json:"children"`
|
|
Child *child `bun:"rel:has-one" json:"child"`
|
|
}
|
|
|
|
handler := &Handler{}
|
|
fields := handler.buildDetailFields(parent{})
|
|
|
|
for _, f := range fields {
|
|
if f.SQLName == "children" || f.SQLName == "child" {
|
|
t.Errorf("relation field %q should not appear in detail fields", f.SQLName)
|
|
}
|
|
}
|
|
|
|
if len(fields) != 2 {
|
|
t.Errorf("expected 2 scalar fields (id, name), got %d", len(fields))
|
|
}
|
|
}
|