171 lines
5.8 KiB
Go
171 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"example.com/nullable/internal/convert"
|
|
"example.com/nullable/internal/domain"
|
|
"example.com/nullable/internal/handler"
|
|
"example.com/nullable/internal/mapping"
|
|
"example.com/nullable/internal/svc"
|
|
"example.com/nullable/internal/types"
|
|
"git.yuntuo.hk/k2safe/go-omni/logx"
|
|
)
|
|
|
|
func TestNullableMappings(t *testing.T) {
|
|
for _, src := range []types.Parent{{}, {Children: []*types.Child{}}, {Child: &types.Child{}, Children: []*types.Child{nil, {Name: "two"}}}} {
|
|
out, err := mapping.ToParent(src)
|
|
if err != nil || (src.Child == nil) != (out.Child == nil) || (src.Children == nil) != (out.Children == nil) || len(src.Children) != len(out.Children) {
|
|
t.Fatal(src, out, err)
|
|
}
|
|
if src.Child != nil && out.Child.Name != src.Child.Name {
|
|
t.Fatal("zero child changed")
|
|
}
|
|
if len(out.Children) > 0 && (out.Children[0] != nil || out.Children[1].Name != "two") {
|
|
t.Fatal(out)
|
|
}
|
|
}
|
|
src := types.Parent{Child: &types.Child{Name: "one"}, Children: []*types.Child{{Name: "two"}}}
|
|
out, err := mapping.ToParentPtr(src)
|
|
if err != nil || out == nil || out.Child.Name != "one" {
|
|
t.Fatal(out, err)
|
|
}
|
|
out.Child.Name = "changed"
|
|
out.Children[0].Name = "changed"
|
|
if src.Child.Name != "one" || src.Children[0].Name != "two" {
|
|
t.Fatal("nested mapped values alias input")
|
|
}
|
|
plain := domain.Parent{Child: &domain.Child{Name: "shared"}}
|
|
copy, err := mapping.ShallowCopy(plain)
|
|
if err != nil || copy.Child != plain.Child {
|
|
t.Fatal("ordinary assignment semantics changed", err)
|
|
}
|
|
zero, err := mapping.RequiredZero(types.Parent{})
|
|
if err != nil || zero.Child.Name != "" {
|
|
t.Fatal(zero, err)
|
|
}
|
|
for _, fn := range []func(types.Parent) (domain.Required, error){mapping.RequiredZero, mapping.RequiredError} {
|
|
out, err := fn(types.Parent{Child: &types.Child{Name: "required"}})
|
|
if err != nil || out.Child.Name != "required" {
|
|
t.Fatal(out, err)
|
|
}
|
|
}
|
|
_, err = mapping.RequiredError(types.Parent{})
|
|
if err == nil || !strings.Contains(err.Error(), "RequiredError.Child") {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = mapping.RequirePointer(types.Parent{})
|
|
if err == nil || !strings.Contains(err.Error(), "RequirePointer.Child") {
|
|
t.Fatal(err)
|
|
}
|
|
lift, err := mapping.Lift(domain.ValueHolder{Child: domain.Child{Name: "lift"}})
|
|
if err != nil || lift.Child == nil || lift.Child.Name != "lift" {
|
|
t.Fatal(lift, err)
|
|
}
|
|
batch, err := mapping.Batch(domain.InputBatch{Fixed: [2]*domain.Child{nil, {Name: "array"}}})
|
|
if err != nil || batch.Fixed[0] != nil || batch.Fixed[1].Name != "array" {
|
|
t.Fatal(batch, err)
|
|
}
|
|
_, err = mapping.Batch(domain.InputBatch{Fixed: [2]*domain.Child{nil, {Name: "bad"}}})
|
|
if !errors.Is(err, convert.ErrValue) || !strings.Contains(err.Error(), "Batch.Fixed[1]") {
|
|
t.Fatal(err)
|
|
}
|
|
for _, input := range []types.Parent{{Child: &types.Child{Name: "bad"}}, {Children: []*types.Child{nil, {Name: "bad"}}}} {
|
|
failed, err := mapping.ToParentPtr(input)
|
|
if failed != nil || !errors.Is(err, convert.ErrValue) || !strings.Contains(err.Error(), "ToChild.Name") {
|
|
t.Fatal(failed, err)
|
|
}
|
|
if len(input.Children) > 0 && !strings.Contains(err.Error(), "ToParent.Children[1]") {
|
|
t.Fatal(err)
|
|
}
|
|
value, err := mapping.ToParent(input)
|
|
if value.Child != nil || value.Children != nil || !errors.Is(err, convert.ErrValue) {
|
|
t.Fatal("partial result escaped", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExplicitNilAndConcurrentCalls(t *testing.T) {
|
|
zero, err := mapping.ScalarZero(domain.ScalarInput{})
|
|
if err != nil || zero != (domain.ScalarOutput{}) {
|
|
t.Fatal(zero, err)
|
|
}
|
|
_, err = mapping.ScalarError(domain.ScalarInput{})
|
|
if err == nil {
|
|
t.Fatal("nil accepted")
|
|
}
|
|
text, count, enabled := "", int64(0), false
|
|
out, err := mapping.ScalarError(domain.ScalarInput{Text: &text, Count: &count, Enabled: &enabled})
|
|
if err != nil || out != (domain.ScalarOutput{}) {
|
|
t.Fatal("non-nil zero rejected", out, err)
|
|
}
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 32; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
out, err := mapping.ToParent(types.Parent{Child: &types.Child{Name: "parallel"}})
|
|
if err != nil || out.Child == nil || out.Child.Name != "parallel" {
|
|
t.Error(out, err)
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestHTTPNullableMapping(t *testing.T) {
|
|
cfg := logx.Default()
|
|
cfg.Writer = io.Discard
|
|
logger, err := logx.New(cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer logger.Close()
|
|
deps, err := svc.New(context.Background(), logger)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer deps.Close()
|
|
app, err := handler.NewApp(deps)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer app.Close()
|
|
for _, body := range []string{`{}`, `{"child":null}`, `{"child":{"name":""}}`, `{"child":{"name":"one"},"children":[null,{"name":"two"}]}`, `{"children":[]}`} {
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest("POST", "/", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
app.Engine().ServeHTTP(w, req)
|
|
if w.Code != 200 {
|
|
t.Fatal(w.Code, w.Body.String())
|
|
}
|
|
var input, output types.Parent
|
|
if err := json.Unmarshal([]byte(body), &input); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &output); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if (input.Child == nil) != (output.Child == nil) || (input.Children == nil) != (output.Children == nil) || len(input.Children) != len(output.Children) {
|
|
t.Fatal(body, w.Body.String())
|
|
}
|
|
if input.Child != nil && input.Child.Name != output.Child.Name {
|
|
t.Fatal(w.Body.String())
|
|
}
|
|
}
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest("POST", "/", strings.NewReader(`{"child":{"name":"bad"}}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
app.Engine().ServeHTTP(w, req)
|
|
if w.Code != 500 || strings.Contains(w.Body.String(), convert.ErrValue.Error()) || strings.Contains(w.Body.String(), "ToChild") {
|
|
t.Fatal("private cause leaked", w.Code, w.Body.String())
|
|
}
|
|
}
|