How to use SuperJSONOf method of td Package

Best Go-testdeep code snippet using td.SuperJSONOf

td_json.go

Source:td_json.go Github

copy

Full Screen

...21 "github.com/maxatome/go-testdeep/internal/types"22 "github.com/maxatome/go-testdeep/internal/util"23)24// forbiddenOpsInJSON contains operators forbidden inside JSON,25// SubJSONOf or SuperJSONOf, optionally with an alternative to help26// the user.27var forbiddenOpsInJSON = map[string]string{28 "Array": "literal []",29 "Cap": "",30 "Catch": "",31 "Code": "",32 "Delay": "",33 "ErrorIs": "",34 "Isa": "",35 "JSON": "literal JSON",36 "Lax": "",37 "Map": "literal {}",38 "PPtr": "",39 "Ptr": "",40 "Recv": "",41 "SStruct": "",42 "Shallow": "",43 "Slice": "literal []",44 "Smuggle": "",45 "String": `literal ""`,46 "SubJSONOf": "SubMapOf operator",47 "SuperJSONOf": "SuperMapOf operator",48 "SuperSliceOf": "All and JSONPointer operators",49 "Struct": "",50 "Tag": "",51 "TruncTime": "",52}53// tdJSONUnmarshaler handles the JSON unmarshaling of JSON, SubJSONOf54// and SuperJSONOf first parameter.55type tdJSONUnmarshaler struct {56 location.Location // position of the operator57}58// newJSONUnmarshaler returns a new instance of tdJSONUnmarshaler.59func newJSONUnmarshaler(pos location.Location) tdJSONUnmarshaler {60 return tdJSONUnmarshaler{61 Location: pos,62 }63}64// replaceLocation replaces the location of tdOp by the65// JSON/SubJSONOf/SuperJSONOf one then add the position of the66// operator inside the JSON string.67func (u tdJSONUnmarshaler) replaceLocation(tdOp TestDeep, posInJSON json.Position) {68 // The goal, instead of:69 // [under operator Len at value.go:476]70 // having:71 // [under operator Len at line 12:7 (pos 123) inside operator JSON at file.go:23]72 // so add ^------------------------------------------^73 newPos := u.Location74 newPos.Inside = fmt.Sprintf("%s inside operator %s ", posInJSON, u.Func)75 newPos.Func = tdOp.GetLocation().Func76 tdOp.replaceLocation(newPos)77}78// unmarshal unmarshals expectedJSON using placeholder parameters params.79func (u tdJSONUnmarshaler) unmarshal(expectedJSON any, params []any) (any, *ctxerr.Error) {80 var (81 err error82 b []byte83 )84 switch data := expectedJSON.(type) {85 case string:86 // Try to load this file (if it seems it can be a filename and not87 // a JSON content)88 if strings.HasSuffix(data, ".json") {89 // It could be a file name, try to read from it90 b, err = os.ReadFile(data)91 if err != nil {92 return nil, ctxerr.OpBad(u.Func, "JSON file %s cannot be read: %s", data, err)93 }94 break95 }96 b = []byte(data)97 case []byte:98 b = data99 case io.Reader:100 b, err = io.ReadAll(data)101 if err != nil {102 return nil, ctxerr.OpBad(u.Func, "JSON read error: %s", err)103 }104 default:105 return nil, ctxerr.OpBadUsage(106 u.Func, "(STRING_JSON|STRING_FILENAME|[]byte|io.Reader, ...)",107 expectedJSON, 1, false)108 }109 params = flat.Interfaces(params...)110 var byTag map[string]any111 for i, p := range params {112 switch op := p.(type) {113 case *tdTag:114 if byTag[op.tag] != nil {115 return nil, ctxerr.OpBad(u.Func, `2 params have the same tag "%s"`, op.tag)116 }117 if byTag == nil {118 byTag = map[string]any{}119 }120 // Don't keep the tag layer121 p = nil122 if op.expectedValue.IsValid() {123 p = op.expectedValue.Interface()124 }125 byTag[op.tag] = newJSONNamedPlaceholder(op.tag, p)126 default:127 params[i] = newJSONNumPlaceholder(uint64(i+1), p)128 }129 }130 final, err := json.Parse(b, json.ParseOpts{131 Placeholders: params,132 PlaceholdersByName: byTag,133 OpFn: u.resolveOp(),134 })135 if err != nil {136 return nil, ctxerr.OpBad(u.Func, "JSON unmarshal error: %s", err)137 }138 return final, nil139}140// resolveOp returns a closure usable as json.ParseOpts.OpFn.141func (u tdJSONUnmarshaler) resolveOp() func(json.Operator, json.Position) (any, error) {142 return func(jop json.Operator, posInJSON json.Position) (any, error) {143 op, exists := allOperators[jop.Name]144 if !exists {145 return nil, fmt.Errorf("unknown operator %s()", jop.Name)146 }147 if hint, exists := forbiddenOpsInJSON[jop.Name]; exists {148 if hint == "" {149 return nil, fmt.Errorf("%s() is not usable in JSON()", jop.Name)150 }151 return nil, fmt.Errorf("%s() is not usable in JSON(), use %s instead",152 jop.Name, hint)153 }154 vfn := reflect.ValueOf(op)155 tfn := vfn.Type()156 // If some parameters contain a placeholder, dereference it157 for i, p := range jop.Params {158 if ph, ok := p.(*tdJSONPlaceholder); ok {159 jop.Params[i] = ph.expectedValue.Interface()160 }161 }162 // Special cases163 var min, max int164 addNilParam := false165 switch jop.Name {166 case "Between":167 min, max = 2, 3168 if len(jop.Params) == 3 {169 bad := false170 switch tp := jop.Params[2].(type) {171 case BoundsKind:172 // Special case, accept numeric values of Bounds*173 // constants, for the case:174 // td.JSON(`Between(40, 42, $1)`, td.BoundsInOut)175 case string:176 switch tp {177 case "[]", "BoundsInIn":178 jop.Params[2] = BoundsInIn179 case "[[", "BoundsInOut":180 jop.Params[2] = BoundsInOut181 case "]]", "BoundsOutIn":182 jop.Params[2] = BoundsOutIn183 case "][", "BoundsOutOut":184 jop.Params[2] = BoundsOutOut185 default:186 bad = true187 }188 default:189 bad = true190 }191 if bad {192 return nil, errors.New(`Between() bad 3rd parameter, use "[]", "[[", "]]" or "]["`)193 }194 }195 case "N", "Re":196 min, max = 1, 2197 case "SubMapOf", "SuperMapOf":198 min, max, addNilParam = 1, 1, true199 default:200 min = tfn.NumIn()201 if tfn.IsVariadic() {202 // for All(expected ...any) → min == 1, as All() is a non-sense203 max = -1204 } else {205 max = min206 }207 }208 if len(jop.Params) < min || (max >= 0 && len(jop.Params) > max) {209 switch {210 case max < 0:211 return nil, fmt.Errorf("%s() requires at least one parameter", jop.Name)212 case max == 0:213 return nil, fmt.Errorf("%s() requires no parameters", jop.Name)214 case min == max:215 if min == 1 {216 return nil, fmt.Errorf("%s() requires only one parameter", jop.Name)217 }218 return nil, fmt.Errorf("%s() requires %d parameters", jop.Name, min)219 default:220 return nil, fmt.Errorf("%s() requires %d or %d parameters", jop.Name, min, max)221 }222 }223 var in []reflect.Value224 if len(jop.Params) > 0 {225 in = make([]reflect.Value, len(jop.Params))226 for i, p := range jop.Params {227 in[i] = reflect.ValueOf(p)228 }229 if addNilParam {230 in = append(in, reflect.ValueOf(MapEntries(nil)))231 }232 // If the function is variadic, no need to check each param as all233 // variadic operators have always a ...any234 numCheck := len(in)235 if tfn.IsVariadic() {236 numCheck = tfn.NumIn() - 1237 }238 for i, p := range in[:numCheck] {239 fpt := tfn.In(i)240 if fpt.Kind() != reflect.Interface && p.Type() != fpt {241 return nil, fmt.Errorf(242 "%s() bad #%d parameter type: %s required but %s received",243 jop.Name, i+1,244 fpt, p.Type(),245 )246 }247 }248 }249 tdOp := vfn.Call(in)[0].Interface().(TestDeep)250 // let erroneous operators (tdOp.err != nil) pass251 // replace the location by the JSON/SubJSONOf/SuperJSONOf one252 u.replaceLocation(tdOp, posInJSON)253 return newJSONEmbedded(tdOp), nil254 }255}256// tdJSONSmuggler is the base type for tdJSONPlaceholder & tdJSONEmbedded.257type tdJSONSmuggler struct {258 tdSmugglerBase // ignored by tools/gen_funcs.pl259}260func (s *tdJSONSmuggler) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {261 vgot, _ := jsonify(ctx, got) // Cannot fail262 // Here, vgot type is either a bool, float64, string,263 // []any, a map[string]any or simply nil264 return s.jsonValueEqual(ctx, vgot)265}266func (s *tdJSONSmuggler) String() string {267 return util.ToString(s.expectedValue.Interface())268}269func (s *tdJSONSmuggler) HandleInvalid() bool {270 return true271}272func (s *tdJSONSmuggler) TypeBehind() reflect.Type {273 return s.internalTypeBehind()274}275// tdJSONPlaceholder is an internal smuggler operator. It represents a276// JSON placeholder in an unmarshaled JSON expected data structure. As $1 in:277//278// td.JSON(`{"foo": $1}`, td.Between(12, 34))279//280// It takes the JSON representation of data and compares it to281// expectedValue.282//283// It does its best to convert back the JSON pointed data to the type284// of expectedValue or to the type behind the expectedValue.285type tdJSONPlaceholder struct {286 tdJSONSmuggler287 name string288 num uint64289}290func newJSONNamedPlaceholder(name string, expectedValue any) TestDeep {291 p := tdJSONPlaceholder{292 tdJSONSmuggler: tdJSONSmuggler{293 tdSmugglerBase: newSmugglerBase(expectedValue, 1),294 },295 name: name,296 }297 if !p.isTestDeeper {298 p.expectedValue = reflect.ValueOf(expectedValue)299 }300 return &p301}302func newJSONNumPlaceholder(num uint64, expectedValue any) TestDeep {303 p := tdJSONPlaceholder{304 tdJSONSmuggler: tdJSONSmuggler{305 tdSmugglerBase: newSmugglerBase(expectedValue, 1),306 },307 num: num,308 }309 if !p.isTestDeeper {310 p.expectedValue = reflect.ValueOf(expectedValue)311 }312 return &p313}314func (p *tdJSONPlaceholder) MarshalJSON() ([]byte, error) {315 if !p.isTestDeeper {316 var expected any317 if p.expectedValue.IsValid() {318 expected = p.expectedValue.Interface()319 }320 return ejson.Marshal(expected)321 }322 var b bytes.Buffer323 if p.num == 0 {324 fmt.Fprintf(&b, `"$%s"`, p.name)325 } else {326 fmt.Fprintf(&b, `"$%d"`, p.num)327 }328 b.WriteString(` /* `)329 indent := "\n" + strings.Repeat(" ", b.Len())330 b.WriteString(strings.ReplaceAll(p.String(), "\n", indent))331 b.WriteString(` */`)332 return b.Bytes(), nil333}334// tdJSONEmbedded represents a MarshalJSON'able operator. As Between() in:335//336// td.JSON(`{"foo": Between(12, 34)}`)337//338// tdSmugglerBase always contains a TestDeep operator, newJSONEmbedded()339// ensures that.340//341// It does its best to convert back the JSON pointed data to the type342// of the type behind the expectedValue (which is always a TestDeep343// operator).344type tdJSONEmbedded struct {345 tdJSONSmuggler346}347func newJSONEmbedded(tdOp TestDeep) TestDeep {348 return &tdJSONEmbedded{349 tdJSONSmuggler: tdJSONSmuggler{350 tdSmugglerBase: newSmugglerBase(tdOp, 1),351 },352 }353}354func (e *tdJSONEmbedded) MarshalJSON() ([]byte, error) {355 return []byte(e.String()), nil356}357// tdJSON is the JSON operator.358type tdJSON struct {359 baseOKNil360 expected reflect.Value361}362var _ TestDeep = &tdJSON{}363func gotViaJSON(ctx ctxerr.Context, pGot *reflect.Value) *ctxerr.Error {364 got, err := jsonify(ctx, *pGot)365 if err != nil {366 return err367 }368 *pGot = reflect.ValueOf(got)369 return nil370}371func jsonify(ctx ctxerr.Context, got reflect.Value) (any, *ctxerr.Error) {372 gotIf, ok := dark.GetInterface(got, true)373 if !ok {374 return nil, ctx.CannotCompareError()375 }376 b, err := ejson.Marshal(gotIf)377 if err != nil {378 if ctx.BooleanError {379 return nil, ctxerr.BooleanError380 }381 return nil, &ctxerr.Error{382 Message: "json.Marshal failed",383 Summary: ctxerr.NewSummary(err.Error()),384 }385 }386 // As Marshal succeeded, Unmarshal in an any cannot fail387 var vgot any388 ejson.Unmarshal(b, &vgot) //nolint: errcheck389 return vgot, nil390}391// summary(JSON): compares against JSON representation392// input(JSON): nil,bool,str,int,float,array,slice,map,struct,ptr393// JSON operator allows to compare the JSON representation of data394// against expectedJSON. expectedJSON can be a:395//396// - string containing JSON data like `{"fullname":"Bob","age":42}`397// - string containing a JSON filename, ending with ".json" (its398// content is [os.ReadFile] before unmarshaling)399// - []byte containing JSON data400// - [io.Reader] stream containing JSON data (is [io.ReadAll]401// before unmarshaling)402//403// expectedJSON JSON value can contain placeholders. The params404// are for any placeholder parameters in expectedJSON. params can405// contain [TestDeep] operators as well as raw values. A placeholder can406// be numeric like $2 or named like $name and always references an407// item in params.408//409// Numeric placeholders reference the n'th "operators" item (starting410// at 1). Named placeholders are used with [Tag] operator as follows:411//412// td.Cmp(t, gotValue,413// td.JSON(`{"fullname": $name, "age": $2, "gender": $3}`,414// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name415// td.Between(41, 43), // matches only $2416// "male")) // matches only $3417//418// Note that placeholders can be double-quoted as in:419//420// td.Cmp(t, gotValue,421// td.JSON(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,422// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name423// td.Between(41, 43), // matches only $2424// "male")) // matches only $3425//426// It makes no difference whatever the underlying type of the replaced427// item is (= double quoting a placeholder matching a number is not a428// problem). It is just a matter of taste, double-quoting placeholders429// can be preferred when the JSON data has to conform to the JSON430// specification, like when used in a ".json" file.431//432// JSON does its best to convert back the JSON corresponding to a433// placeholder to the type of the placeholder or, if the placeholder434// is an operator, to the type behind the operator. Allowing to do435// things like:436//437// td.Cmp(t, gotValue, td.JSON(`{"foo":$1}`, []int{1, 2, 3, 4}))438// td.Cmp(t, gotValue,439// td.JSON(`{"foo":$1}`, []any{1, 2, td.Between(2, 4), 4}))440// td.Cmp(t, gotValue, td.JSON(`{"foo":$1}`, td.Between(27, 32)))441//442// Of course, it does this conversion only if the expected type can be443// guessed. In the case the conversion cannot occur, data is compared444// as is, in its freshly unmarshaled JSON form (so as bool, float64,445// string, []any, map[string]any or simply nil).446//447// Note expectedJSON can be a []byte, a JSON filename or a [io.Reader]:448//449// td.Cmp(t, gotValue, td.JSON("file.json", td.Between(12, 34)))450// td.Cmp(t, gotValue, td.JSON([]byte(`[1, $1, 3]`), td.Between(12, 34)))451// td.Cmp(t, gotValue, td.JSON(osFile, td.Between(12, 34)))452//453// A JSON filename ends with ".json".454//455// To avoid a legit "$" string prefix causes a bad placeholder error,456// just double it to escape it. Note it is only needed when the "$" is457// the first character of a string:458//459// td.Cmp(t, gotValue,460// td.JSON(`{"fullname": "$name", "details": "$$info", "age": $2}`,461// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name462// td.Between(41, 43))) // matches only $2463//464// For the "details" key, the raw value "$info" is expected, no465// placeholders are involved here.466//467// Note that [Lax] mode is automatically enabled by JSON operator to468// simplify numeric tests.469//470// Comments can be embedded in JSON data:471//472// td.Cmp(t, gotValue,473// td.JSON(`474// {475// // A guy properties:476// "fullname": "$name", // The full name of the guy477// "details": "$$info", // Literally "$info", thanks to "$" escape478// "age": $2 /* The age of the guy:479// - placeholder unquoted, but could be without480// any change481// - to demonstrate a multi-lines comment */482// }`,483// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name484// td.Between(41, 43))) // matches only $2485//486// Comments, like in go, have 2 forms. To quote the Go language specification:487// - line comments start with the character sequence // and stop at the488// end of the line.489// - multi-lines comments start with the character sequence /* and stop490// with the first subsequent character sequence */.491//492// Other JSON divergences:493// - ',' can precede a '}' or a ']' (as in go);494// - strings can contain non-escaped \n, \r and \t;495// - raw strings are accepted (r{raw}, r!raw!, …), see below;496// - int_lit & float_lit numbers as defined in go spec are accepted;497// - numbers can be prefixed by '+'.498//499// Most operators can be directly embedded in JSON without requiring500// any placeholder. If an operators does not take any parameter, the501// parenthesis can be omitted.502//503// td.Cmp(t, gotValue,504// td.JSON(`505// {506// "fullname": HasPrefix("Foo"),507// "age": Between(41, 43),508// "details": SuperMapOf({509// "address": NotEmpty, // () are optional when no parameters510// "car": Any("Peugeot", "Tesla", "Jeep") // any of these511// })512// }`))513//514// Placeholders can be used anywhere, even in operators parameters as in:515//516// td.Cmp(t, gotValue, td.JSON(`{"fullname": HasPrefix($1)}`, "Zip"))517//518// A few notes about operators embedding:519// - [SubMapOf] and [SuperMapOf] take only one parameter, a JSON object;520// - the optional 3rd parameter of [Between] has to be specified as a string521// and can be: "[]" or "BoundsInIn" (default), "[[" or "BoundsInOut",522// "]]" or "BoundsOutIn", "][" or "BoundsOutOut";523// - not all operators are embeddable only the following are: [All],524// [Any], [ArrayEach], [Bag], [Between], [Contains],525// [ContainsKey], [Empty], [First], [Grep], [Gt], [Gte],526// [HasPrefix], [HasSuffix], [Ignore], [JSONPointer], [Keys],527// [Last], [Len], [Lt], [Lte], [MapEach], [N], [NaN], [Nil],528// [None], [Not], [NotAny], [NotEmpty], [NotNaN], [NotNil],529// [NotZero], [Re], [ReAll], [Set], [SubBagOf], [SubMapOf],530// [SubSetOf], [SuperBagOf], [SuperMapOf], [SuperSetOf], [Values]531// and [Zero].532//533// It is also possible to embed operators in JSON strings. This way,534// the JSON specification can be fulfilled. To avoid collision with535// possible strings, just prefix the first operator name with536// "$^". The previous example becomes:537//538// td.Cmp(t, gotValue,539// td.JSON(`540// {541// "fullname": "$^HasPrefix(\"Foo\")",542// "age": "$^Between(41, 43)",543// "details": "$^SuperMapOf({544// \"address\": NotEmpty, // () are optional when no parameters545// \"car\": Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these546// })"547// }`))548//549// As you can see, in this case, strings in strings have to be550// escaped. Fortunately, newlines are accepted, but unfortunately they551// are forbidden by JSON specification. To avoid too much escaping,552// raw strings are accepted. A raw string is a "r" followed by a553// delimiter, the corresponding delimiter closes the string. The554// following raw strings are all the same as "foo\\bar(\"zip\")!":555// - r'foo\bar"zip"!'556// - r,foo\bar"zip"!,557// - r%foo\bar"zip"!%558// - r(foo\bar("zip")!)559// - r{foo\bar("zip")!}560// - r[foo\bar("zip")!]561// - r<foo\bar("zip")!>562//563// So non-bracketing delimiters use the same character before and564// after, but the 4 sorts of ASCII brackets (round, angle, square,565// curly) all nest: r[x[y]z] equals "x[y]z". The end delimiter cannot566// be escaped.567//568// With raw strings, the previous example becomes:569//570// td.Cmp(t, gotValue,571// td.JSON(`572// {573// "fullname": "$^HasPrefix(r<Foo>)",574// "age": "$^Between(41, 43)",575// "details": "$^SuperMapOf({576// r<address>: NotEmpty, // () are optional when no parameters577// r<car>: Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these578// })"579// }`))580//581// Note that raw strings are accepted anywhere, not only in original582// JSON strings.583//584// To be complete, $^ can prefix an operator even outside a585// string. This is accepted for compatibility purpose as the first586// operator embedding feature used this way to embed some operators.587//588// So the following calls are all equivalent:589//590// td.Cmp(t, gotValue, td.JSON(`{"id": $1}`, td.NotZero()))591// td.Cmp(t, gotValue, td.JSON(`{"id": NotZero}`))592// td.Cmp(t, gotValue, td.JSON(`{"id": NotZero()}`))593// td.Cmp(t, gotValue, td.JSON(`{"id": $^NotZero}`))594// td.Cmp(t, gotValue, td.JSON(`{"id": $^NotZero()}`))595// td.Cmp(t, gotValue, td.JSON(`{"id": "$^NotZero"}`))596// td.Cmp(t, gotValue, td.JSON(`{"id": "$^NotZero()"}`))597//598// As for placeholders, there is no differences between $^NotZero and599// "$^NotZero".600//601// TypeBehind method returns the [reflect.Type] of the expectedJSON602// once JSON unmarshaled. So it can be bool, string, float64, []any,603// map[string]any or any in case expectedJSON is "null".604//605// See also [JSONPointer], [SubJSONOf] and [SuperJSONOf].606func JSON(expectedJSON any, params ...any) TestDeep {607 j := &tdJSON{608 baseOKNil: newBaseOKNil(3),609 }610 v, err := newJSONUnmarshaler(j.GetLocation()).unmarshal(expectedJSON, params)611 if err != nil {612 j.err = err613 } else {614 j.expected = reflect.ValueOf(v)615 }616 return j617}618func (j *tdJSON) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {619 if j.err != nil {620 return ctx.CollectError(j.err)621 }622 err := gotViaJSON(ctx, &got)623 if err != nil {624 return ctx.CollectError(err)625 }626 ctx.BeLax = true627 return deepValueEqual(ctx, got, j.expected)628}629func (j *tdJSON) String() string {630 if j.err != nil {631 return j.stringError()632 }633 return jsonStringify("JSON", j.expected)634}635func jsonStringify(opName string, v reflect.Value) string {636 if !v.IsValid() {637 return "JSON(null)"638 }639 var b bytes.Buffer640 b.WriteString(opName)641 b.WriteByte('(')642 json.AppendMarshal(&b, v.Interface(), len(opName)+1) //nolint: errcheck643 b.WriteByte(')')644 return b.String()645}646func (j *tdJSON) TypeBehind() reflect.Type {647 if j.err != nil {648 return nil649 }650 if j.expected.IsValid() {651 // In case we have an operator at the root, delegate it the call652 if tdOp, ok := j.expected.Interface().(TestDeep); ok {653 return tdOp.TypeBehind()654 }655 return j.expected.Type()656 }657 return types.Interface658}659type tdMapJSON struct {660 tdMap661 expected reflect.Value662}663var _ TestDeep = &tdMapJSON{}664// summary(SubJSONOf): compares struct or map against JSON665// representation but with potentially some exclusions666// input(SubJSONOf): map,struct,ptr(ptr on map/struct)667// SubJSONOf operator allows to compare the JSON representation of668// data against expectedJSON. Unlike [JSON] operator, marshaled data669// must be a JSON object/map (aka {…}). expectedJSON can be a:670//671// - string containing JSON data like `{"fullname":"Bob","age":42}`672// - string containing a JSON filename, ending with ".json" (its673// content is [os.ReadFile] before unmarshaling)674// - []byte containing JSON data675// - [io.Reader] stream containing JSON data (is [io.ReadAll] before676// unmarshaling)677//678// JSON data contained in expectedJSON must be a JSON object/map679// (aka {…}) too. During a match, each expected entry should match in680// the compared map. But some expected entries can be missing from the681// compared map.682//683// type MyStruct struct {684// Name string `json:"name"`685// Age int `json:"age"`686// }687// got := MyStruct{688// Name: "Bob",689// Age: 42,690// }691// td.Cmp(t, got, td.SubJSONOf(`{"name": "Bob", "age": 42, "city": "NY"}`)) // succeeds692// td.Cmp(t, got, td.SubJSONOf(`{"name": "Bob", "zip": 666}`)) // fails, extra "age"693//694// expectedJSON JSON value can contain placeholders. The params695// are for any placeholder parameters in expectedJSON. params can696// contain [TestDeep] operators as well as raw values. A placeholder can697// be numeric like $2 or named like $name and always references an698// item in params.699//700// Numeric placeholders reference the n'th "operators" item (starting701// at 1). Named placeholders are used with [Tag] operator as follows:702//703// td.Cmp(t, gotValue,704// td.SubJSONOf(`{"fullname": $name, "age": $2, "gender": $3}`,705// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name706// td.Between(41, 43), // matches only $2707// "male")) // matches only $3708//709// Note that placeholders can be double-quoted as in:710//711// td.Cmp(t, gotValue,712// td.SubJSONOf(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,713// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name714// td.Between(41, 43), // matches only $2715// "male")) // matches only $3716//717// It makes no difference whatever the underlying type of the replaced718// item is (= double quoting a placeholder matching a number is not a719// problem). It is just a matter of taste, double-quoting placeholders720// can be preferred when the JSON data has to conform to the JSON721// specification, like when used in a ".json" file.722//723// SubJSONOf does its best to convert back the JSON corresponding to a724// placeholder to the type of the placeholder or, if the placeholder725// is an operator, to the type behind the operator. Allowing to do726// things like:727//728// td.Cmp(t, gotValue,729// td.SubJSONOf(`{"foo":$1, "bar": 12}`, []int{1, 2, 3, 4}))730// td.Cmp(t, gotValue,731// td.SubJSONOf(`{"foo":$1, "bar": 12}`, []any{1, 2, td.Between(2, 4), 4}))732// td.Cmp(t, gotValue,733// td.SubJSONOf(`{"foo":$1, "bar": 12}`, td.Between(27, 32)))734//735// Of course, it does this conversion only if the expected type can be736// guessed. In the case the conversion cannot occur, data is compared737// as is, in its freshly unmarshaled JSON form (so as bool, float64,738// string, []any, map[string]any or simply nil).739//740// Note expectedJSON can be a []byte, JSON filename or [io.Reader]:741//742// td.Cmp(t, gotValue, td.SubJSONOf("file.json", td.Between(12, 34)))743// td.Cmp(t, gotValue, td.SubJSONOf([]byte(`[1, $1, 3]`), td.Between(12, 34)))744// td.Cmp(t, gotValue, td.SubJSONOf(osFile, td.Between(12, 34)))745//746// A JSON filename ends with ".json".747//748// To avoid a legit "$" string prefix causes a bad placeholder error,749// just double it to escape it. Note it is only needed when the "$" is750// the first character of a string:751//752// td.Cmp(t, gotValue,753// td.SubJSONOf(`{"fullname": "$name", "details": "$$info", "age": $2}`,754// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name755// td.Between(41, 43))) // matches only $2756//757// For the "details" key, the raw value "$info" is expected, no758// placeholders are involved here.759//760// Note that [Lax] mode is automatically enabled by SubJSONOf operator to761// simplify numeric tests.762//763// Comments can be embedded in JSON data:764//765// td.Cmp(t, gotValue,766// SubJSONOf(`767// {768// // A guy properties:769// "fullname": "$name", // The full name of the guy770// "details": "$$info", // Literally "$info", thanks to "$" escape771// "age": $2 /* The age of the guy:772// - placeholder unquoted, but could be without773// any change774// - to demonstrate a multi-lines comment */775// }`,776// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name777// td.Between(41, 43))) // matches only $2778//779// Comments, like in go, have 2 forms. To quote the Go language specification:780// - line comments start with the character sequence // and stop at the781// end of the line.782// - multi-lines comments start with the character sequence /* and stop783// with the first subsequent character sequence */.784//785// Other JSON divergences:786// - ',' can precede a '}' or a ']' (as in go);787// - strings can contain non-escaped \n, \r and \t;788// - raw strings are accepted (r{raw}, r!raw!, …), see below;789// - int_lit & float_lit numbers as defined in go spec are accepted;790// - numbers can be prefixed by '+'.791//792// Most operators can be directly embedded in SubJSONOf without requiring793// any placeholder. If an operators does not take any parameter, the794// parenthesis can be omitted.795//796// td.Cmp(t, gotValue,797// td.SubJSONOf(`798// {799// "fullname": HasPrefix("Foo"),800// "age": Between(41, 43),801// "details": SuperMapOf({802// "address": NotEmpty, // () are optional when no parameters803// "car": Any("Peugeot", "Tesla", "Jeep") // any of these804// })805// }`))806//807// Placeholders can be used anywhere, even in operators parameters as in:808//809// td.Cmp(t, gotValue,810// td.SubJSONOf(`{"fullname": HasPrefix($1), "bar": 42}`, "Zip"))811//812// A few notes about operators embedding:813// - [SubMapOf] and [SuperMapOf] take only one parameter, a JSON object;814// - the optional 3rd parameter of [Between] has to be specified as a string815// and can be: "[]" or "BoundsInIn" (default), "[[" or "BoundsInOut",816// "]]" or "BoundsOutIn", "][" or "BoundsOutOut";817// - not all operators are embeddable only the following are: [All],818// [Any], [ArrayEach], [Bag], [Between], [Contains],819// [ContainsKey], [Empty], [First], [Grep], [Gt], [Gte],820// [HasPrefix], [HasSuffix], [Ignore], [JSONPointer], [Keys],821// [Last], [Len], [Lt], [Lte], [MapEach], [N], [NaN], [Nil],822// [None], [Not], [NotAny], [NotEmpty], [NotNaN], [NotNil],823// [NotZero], [Re], [ReAll], [Set], [SubBagOf], [SubMapOf],824// [SubSetOf], [SuperBagOf], [SuperMapOf], [SuperSetOf], [Values]825// and [Zero].826//827// It is also possible to embed operators in JSON strings. This way,828// the JSON specification can be fulfilled. To avoid collision with829// possible strings, just prefix the first operator name with830// "$^". The previous example becomes:831//832// td.Cmp(t, gotValue,833// td.SubJSONOf(`834// {835// "fullname": "$^HasPrefix(\"Foo\")",836// "age": "$^Between(41, 43)",837// "details": "$^SuperMapOf({838// \"address\": NotEmpty, // () are optional when no parameters839// \"car\": Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these840// })"841// }`))842//843// As you can see, in this case, strings in strings have to be844// escaped. Fortunately, newlines are accepted, but unfortunately they845// are forbidden by JSON specification. To avoid too much escaping,846// raw strings are accepted. A raw string is a "r" followed by a847// delimiter, the corresponding delimiter closes the string. The848// following raw strings are all the same as "foo\\bar(\"zip\")!":849// - r'foo\bar"zip"!'850// - r,foo\bar"zip"!,851// - r%foo\bar"zip"!%852// - r(foo\bar("zip")!)853// - r{foo\bar("zip")!}854// - r[foo\bar("zip")!]855// - r<foo\bar("zip")!>856//857// So non-bracketing delimiters use the same character before and858// after, but the 4 sorts of ASCII brackets (round, angle, square,859// curly) all nest: r[x[y]z] equals "x[y]z". The end delimiter cannot860// be escaped.861//862// With raw strings, the previous example becomes:863//864// td.Cmp(t, gotValue,865// td.SubJSONOf(`866// {867// "fullname": "$^HasPrefix(r<Foo>)",868// "age": "$^Between(41, 43)",869// "details": "$^SuperMapOf({870// r<address>: NotEmpty, // () are optional when no parameters871// r<car>: Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these872// })"873// }`))874//875// Note that raw strings are accepted anywhere, not only in original876// JSON strings.877//878// To be complete, $^ can prefix an operator even outside a879// string. This is accepted for compatibility purpose as the first880// operator embedding feature used this way to embed some operators.881//882// So the following calls are all equivalent:883//884// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $1}`, td.NotZero()))885// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": NotZero}`))886// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": NotZero()}`))887// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $^NotZero}`))888// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $^NotZero()}`))889// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": "$^NotZero"}`))890// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": "$^NotZero()"}`))891//892// As for placeholders, there is no differences between $^NotZero and893// "$^NotZero".894//895// TypeBehind method returns the map[string]any type.896//897// See also [JSON], [JSONPointer] and [SuperJSONOf].898func SubJSONOf(expectedJSON any, params ...any) TestDeep {899 m := &tdMapJSON{900 tdMap: tdMap{901 tdExpectedType: tdExpectedType{902 base: newBase(3),903 expectedType: reflect.TypeOf((map[string]any)(nil)),904 },905 kind: subMap,906 },907 }908 v, err := newJSONUnmarshaler(m.GetLocation()).unmarshal(expectedJSON, params)909 if err != nil {910 m.err = err911 return m912 }913 _, ok := v.(map[string]any)914 if !ok {915 m.err = ctxerr.OpBad("SubJSONOf", "SubJSONOf() only accepts JSON objects {…}")916 return m917 }918 m.expected = reflect.ValueOf(v)919 m.populateExpectedEntries(nil, m.expected)920 return m921}922// summary(SuperJSONOf): compares struct or map against JSON923// representation but with potentially extra entries924// input(SuperJSONOf): map,struct,ptr(ptr on map/struct)925// SuperJSONOf operator allows to compare the JSON representation of926// data against expectedJSON. Unlike JSON operator, marshaled data927// must be a JSON object/map (aka {…}). expectedJSON can be a:928//929// - string containing JSON data like `{"fullname":"Bob","age":42}`930// - string containing a JSON filename, ending with ".json" (its931// content is [os.ReadFile] before unmarshaling)932// - []byte containing JSON data933// - [io.Reader] stream containing JSON data (is [io.ReadAll] before934// unmarshaling)935//936// JSON data contained in expectedJSON must be a JSON object/map937// (aka {…}) too. During a match, each expected entry should match in938// the compared map. But some entries in the compared map may not be939// expected.940//941// type MyStruct struct {942// Name string `json:"name"`943// Age int `json:"age"`944// City string `json:"city"`945// }946// got := MyStruct{947// Name: "Bob",948// Age: 42,949// City: "TestCity",950// }951// td.Cmp(t, got, td.SuperJSONOf(`{"name": "Bob", "age": 42}`)) // succeeds952// td.Cmp(t, got, td.SuperJSONOf(`{"name": "Bob", "zip": 666}`)) // fails, miss "zip"953//954// expectedJSON JSON value can contain placeholders. The params are955// for any placeholder parameters in expectedJSON. params can contain956// [TestDeep] operators as well as raw values. A placeholder can be957// numeric like $2 or named like $name and always references an item958// in params.959//960// Numeric placeholders reference the n'th "operators" item (starting961// at 1). Named placeholders are used with [Tag] operator as follows:962//963// td.Cmp(t, gotValue,964// SuperJSONOf(`{"fullname": $name, "age": $2, "gender": $3}`,965// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name966// td.Between(41, 43), // matches only $2967// "male")) // matches only $3968//969// Note that placeholders can be double-quoted as in:970//971// td.Cmp(t, gotValue,972// td.SuperJSONOf(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,973// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name974// td.Between(41, 43), // matches only $2975// "male")) // matches only $3976//977// It makes no difference whatever the underlying type of the replaced978// item is (= double quoting a placeholder matching a number is not a979// problem). It is just a matter of taste, double-quoting placeholders980// can be preferred when the JSON data has to conform to the JSON981// specification, like when used in a ".json" file.982//983// SuperJSONOf does its best to convert back the JSON corresponding to a984// placeholder to the type of the placeholder or, if the placeholder985// is an operator, to the type behind the operator. Allowing to do986// things like:987//988// td.Cmp(t, gotValue,989// td.SuperJSONOf(`{"foo":$1}`, []int{1, 2, 3, 4}))990// td.Cmp(t, gotValue,991// td.SuperJSONOf(`{"foo":$1}`, []any{1, 2, td.Between(2, 4), 4}))992// td.Cmp(t, gotValue,993// td.SuperJSONOf(`{"foo":$1}`, td.Between(27, 32)))994//995// Of course, it does this conversion only if the expected type can be996// guessed. In the case the conversion cannot occur, data is compared997// as is, in its freshly unmarshaled JSON form (so as bool, float64,998// string, []any, map[string]any or simply nil).999//1000// Note expectedJSON can be a []byte, JSON filename or [io.Reader]:1001//1002// td.Cmp(t, gotValue, td.SuperJSONOf("file.json", td.Between(12, 34)))1003// td.Cmp(t, gotValue, td.SuperJSONOf([]byte(`[1, $1, 3]`), td.Between(12, 34)))1004// td.Cmp(t, gotValue, td.SuperJSONOf(osFile, td.Between(12, 34)))1005//1006// A JSON filename ends with ".json".1007//1008// To avoid a legit "$" string prefix causes a bad placeholder error,1009// just double it to escape it. Note it is only needed when the "$" is1010// the first character of a string:1011//1012// td.Cmp(t, gotValue,1013// td.SuperJSONOf(`{"fullname": "$name", "details": "$$info", "age": $2}`,1014// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name1015// td.Between(41, 43))) // matches only $21016//1017// For the "details" key, the raw value "$info" is expected, no1018// placeholders are involved here.1019//1020// Note that [Lax] mode is automatically enabled by SuperJSONOf operator to1021// simplify numeric tests.1022//1023// Comments can be embedded in JSON data:1024//1025// td.Cmp(t, gotValue,1026// td.SuperJSONOf(`1027// {1028// // A guy properties:1029// "fullname": "$name", // The full name of the guy1030// "details": "$$info", // Literally "$info", thanks to "$" escape1031// "age": $2 /* The age of the guy:1032// - placeholder unquoted, but could be without1033// any change1034// - to demonstrate a multi-lines comment */1035// }`,1036// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name1037// td.Between(41, 43))) // matches only $21038//1039// Comments, like in go, have 2 forms. To quote the Go language specification:1040// - line comments start with the character sequence // and stop at the1041// end of the line.1042// - multi-lines comments start with the character sequence /* and stop1043// with the first subsequent character sequence */.1044//1045// Other JSON divergences:1046// - ',' can precede a '}' or a ']' (as in go);1047// - strings can contain non-escaped \n, \r and \t;1048// - raw strings are accepted (r{raw}, r!raw!, …), see below;1049// - int_lit & float_lit numbers as defined in go spec are accepted;1050// - numbers can be prefixed by '+'.1051//1052// Most operators can be directly embedded in SuperJSONOf without requiring1053// any placeholder. If an operators does not take any parameter, the1054// parenthesis can be omitted.1055//1056// td.Cmp(t, gotValue,1057// td.SuperJSONOf(`1058// {1059// "fullname": HasPrefix("Foo"),1060// "age": Between(41, 43),1061// "details": SuperMapOf({1062// "address": NotEmpty, // () are optional when no parameters1063// "car": Any("Peugeot", "Tesla", "Jeep") // any of these1064// })1065// }`))1066//1067// Placeholders can be used anywhere, even in operators parameters as in:1068//1069// td.Cmp(t, gotValue, td.SuperJSONOf(`{"fullname": HasPrefix($1)}`, "Zip"))1070//1071// A few notes about operators embedding:1072// - [SubMapOf] and [SuperMapOf] take only one parameter, a JSON object;1073// - the optional 3rd parameter of [Between] has to be specified as a string1074// and can be: "[]" or "BoundsInIn" (default), "[[" or "BoundsInOut",1075// "]]" or "BoundsOutIn", "][" or "BoundsOutOut";1076// - not all operators are embeddable only the following are: [All],1077// [Any], [ArrayEach], [Bag], [Between], [Contains],1078// [ContainsKey], [Empty], [First], [Grep], [Gt], [Gte],1079// [HasPrefix], [HasSuffix], [Ignore], [JSONPointer], [Keys],1080// [Last], [Len], [Lt], [Lte], [MapEach], [N], [NaN], [Nil],1081// [None], [Not], [NotAny], [NotEmpty], [NotNaN], [NotNil],1082// [NotZero], [Re], [ReAll], [Set], [SubBagOf], [SubMapOf],1083// [SubSetOf], [SuperBagOf], [SuperMapOf], [SuperSetOf], [Values]1084// and [Zero].1085//1086// It is also possible to embed operators in JSON strings. This way,1087// the JSON specification can be fulfilled. To avoid collision with1088// possible strings, just prefix the first operator name with1089// "$^". The previous example becomes:1090//1091// td.Cmp(t, gotValue,1092// td.SuperJSONOf(`1093// {1094// "fullname": "$^HasPrefix(\"Foo\")",1095// "age": "$^Between(41, 43)",1096// "details": "$^SuperMapOf({1097// \"address\": NotEmpty, // () are optional when no parameters1098// \"car\": Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these1099// })"1100// }`))1101//1102// As you can see, in this case, strings in strings have to be1103// escaped. Fortunately, newlines are accepted, but unfortunately they1104// are forbidden by JSON specification. To avoid too much escaping,1105// raw strings are accepted. A raw string is a "r" followed by a1106// delimiter, the corresponding delimiter closes the string. The1107// following raw strings are all the same as "foo\\bar(\"zip\")!":1108// - r'foo\bar"zip"!'1109// - r,foo\bar"zip"!,1110// - r%foo\bar"zip"!%1111// - r(foo\bar("zip")!)1112// - r{foo\bar("zip")!}1113// - r[foo\bar("zip")!]1114// - r<foo\bar("zip")!>1115//1116// So non-bracketing delimiters use the same character before and1117// after, but the 4 sorts of ASCII brackets (round, angle, square,1118// curly) all nest: r[x[y]z] equals "x[y]z". The end delimiter cannot1119// be escaped.1120//1121// With raw strings, the previous example becomes:1122//1123// td.Cmp(t, gotValue,1124// td.SuperJSONOf(`1125// {1126// "fullname": "$^HasPrefix(r<Foo>)",1127// "age": "$^Between(41, 43)",1128// "details": "$^SuperMapOf({1129// r<address>: NotEmpty, // () are optional when no parameters1130// r<car>: Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these1131// })"1132// }`))1133//1134// Note that raw strings are accepted anywhere, not only in original1135// JSON strings.1136//1137// To be complete, $^ can prefix an operator even outside a1138// string. This is accepted for compatibility purpose as the first1139// operator embedding feature used this way to embed some operators.1140//1141// So the following calls are all equivalent:1142//1143// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $1}`, td.NotZero()))1144// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": NotZero}`))1145// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": NotZero()}`))1146// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $^NotZero}`))1147// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $^NotZero()}`))1148// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": "$^NotZero"}`))1149// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": "$^NotZero()"}`))1150//1151// As for placeholders, there is no differences between $^NotZero and1152// "$^NotZero".1153//1154// TypeBehind method returns the map[string]any type.1155//1156// See also [JSON], [JSONPointer] and [SubJSONOf].1157func SuperJSONOf(expectedJSON any, params ...any) TestDeep {1158 m := &tdMapJSON{1159 tdMap: tdMap{1160 tdExpectedType: tdExpectedType{1161 base: newBase(3),1162 expectedType: reflect.TypeOf((map[string]any)(nil)),1163 },1164 kind: superMap,1165 },1166 }1167 v, err := newJSONUnmarshaler(m.GetLocation()).unmarshal(expectedJSON, params)1168 if err != nil {1169 m.err = err1170 return m1171 }1172 _, ok := v.(map[string]any)1173 if !ok {1174 m.err = ctxerr.OpBad("SuperJSONOf", "SuperJSONOf() only accepts JSON objects {…}")1175 return m1176 }1177 m.expected = reflect.ValueOf(v)1178 m.populateExpectedEntries(nil, m.expected)1179 return m1180}1181func (m *tdMapJSON) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {1182 if m.err != nil {1183 return ctx.CollectError(m.err)1184 }1185 err := gotViaJSON(ctx, &got)1186 if err != nil {1187 return ctx.CollectError(err)1188 }...

Full Screen

Full Screen

td_json_test.go

Source:td_json_test.go Github

copy

Full Screen

...885 equalTypes(t, td.SubJSONOf(`{"a":12}`), (map[string]any)(nil))886 // Erroneous op887 equalTypes(t, td.SubJSONOf(`123`), nil)888}889func TestSuperJSONOf(t *testing.T) {890 type MyStruct struct {891 Name string `json:"name"`892 Age uint `json:"age"`893 Gender string `json:"gender"`894 Details string `json:"details"`895 }896 //897 // struct898 //899 got := MyStruct{Name: "Bob", Age: 42, Gender: "male", Details: "Nice"}900 // No placeholder901 checkOK(t, got, td.SuperJSONOf(`{"name": "Bob"}`))902 // Numeric placeholders903 checkOK(t, got,904 td.SuperJSONOf(`{"name":"$1","age":$2}`,905 "Bob", 42)) // raw values906 checkOK(t, got,907 td.SuperJSONOf(`{"name":"$1","age":$2}`,908 td.Re(`^Bob`),909 td.Between(40, 45)))910 // Same using Flatten911 checkOK(t, got,912 td.SuperJSONOf(`{"name":"$1","age":$2}`,913 td.Flatten([]td.TestDeep{td.Re(`^Bob`), td.Between(40, 45)}),914 ))915 // Tag placeholders916 checkOK(t, got,917 td.SuperJSONOf(`{"name":"$name","gender":"$gender"}`,918 td.Tag("name", td.Re(`^Bob`)),919 td.Tag("gender", td.NotEmpty())))920 // Mixed placeholders + operator921 for _, op := range []string{922 "NotEmpty",923 "NotEmpty()",924 "$^NotEmpty",925 "$^NotEmpty()",926 `"$^NotEmpty"`,927 `"$^NotEmpty()"`,928 `r<$^NotEmpty>`,929 `r<$^NotEmpty()>`,930 } {931 checkOK(t, got,932 td.SuperJSONOf(933 `{"name":"$name","age":$1,"gender":`+op+`}`,934 td.Tag("age", td.Between(40, 45)),935 td.Tag("name", td.Re(`^Bob`))),936 "using operator %s", op)937 }938 // …with comments…939 checkOK(t, got,940 td.SuperJSONOf(`941// This should be the JSON representation of MyStruct struct942{943 // A person:944 "name": "$name", // The name of this person945 "age": $1, /* The age of this person:946 - placeholder unquoted, but could be without947 any change948 - to demonstrate a multi-lines comment */949 "gender": $^NotEmpty // Shortcut to operator NotEmpty950}`,951 td.Tag("age", td.Between(40, 45)),952 td.Tag("name", td.Re(`^Bob`))))953 //954 // Errors955 checkError(t, func() {}, td.SuperJSONOf(`{}`),956 expectedError{957 Message: mustBe("json.Marshal failed"),958 Summary: mustContain("json: unsupported type"),959 })960 for i, n := range []any{961 nil,962 (map[string]any)(nil),963 (map[string]bool)(nil),964 ([]int)(nil),965 } {966 checkError(t, n, td.SuperJSONOf(`{}`),967 expectedError{968 Message: mustBe("values differ"),969 Got: mustBe("null"),970 Expected: mustBe("non-null"),971 },972 "nil test #%d", i)973 }974 //975 // Fatal errors976 checkError(t, "never tested",977 td.SuperJSONOf(`[1, "$123bad"]`),978 expectedError{979 Message: mustBe("bad usage of SuperJSONOf operator"),980 Path: mustBe("DATA"),981 Summary: mustBe(`JSON unmarshal error: invalid numeric placeholder at line 1:5 (pos 5)`),982 })983 checkError(t, "never tested",984 td.SuperJSONOf(`[1, $000]`),985 expectedError{986 Message: mustBe("bad usage of SuperJSONOf operator"),987 Path: mustBe("DATA"),988 Summary: mustBe(`JSON unmarshal error: invalid numeric placeholder "$000", it should start at "$1" at line 1:4 (pos 4)`),989 })990 checkError(t, "never tested",991 td.SuperJSONOf(`[1, $1]`),992 expectedError{993 Message: mustBe("bad usage of SuperJSONOf operator"),994 Path: mustBe("DATA"),995 Summary: mustBe(`JSON unmarshal error: numeric placeholder "$1", but no params given at line 1:4 (pos 4)`),996 })997 checkError(t, "never tested",998 td.SuperJSONOf(`[1, 2, $3]`, td.Ignore()),999 expectedError{1000 Message: mustBe("bad usage of SuperJSONOf operator"),1001 Path: mustBe("DATA"),1002 Summary: mustBe(`JSON unmarshal error: numeric placeholder "$3", but only one param given at line 1:7 (pos 7)`),1003 })1004 // $^Operator1005 checkError(t, "never tested",1006 td.SuperJSONOf(`[1, $^bad%]`),1007 expectedError{1008 Message: mustBe("bad usage of SuperJSONOf operator"),1009 Path: mustBe("DATA"),1010 Summary: mustBe(`JSON unmarshal error: $^ must be followed by an operator name at line 1:4 (pos 4)`),1011 })1012 checkError(t, "never tested",1013 td.SuperJSONOf(`[1, "$^bad%"]`),1014 expectedError{1015 Message: mustBe("bad usage of SuperJSONOf operator"),1016 Path: mustBe("DATA"),1017 Summary: mustBe(`JSON unmarshal error: $^ must be followed by an operator name at line 1:5 (pos 5)`),1018 })1019 // named placeholders1020 checkError(t, "never tested",1021 td.SuperJSONOf(`[1, "$bad%"]`),1022 expectedError{1023 Message: mustBe("bad usage of SuperJSONOf operator"),1024 Path: mustBe("DATA"),1025 Summary: mustBe(`JSON unmarshal error: bad placeholder "$bad%" at line 1:5 (pos 5)`),1026 })1027 checkError(t, "never tested",1028 td.SuperJSONOf(`[1, $unknown]`),1029 expectedError{1030 Message: mustBe("bad usage of SuperJSONOf operator"),1031 Path: mustBe("DATA"),1032 Summary: mustBe(`JSON unmarshal error: unknown placeholder "$unknown" at line 1:4 (pos 4)`),1033 })1034 checkError(t, "never tested",1035 td.SuperJSONOf("null"),1036 expectedError{1037 Message: mustBe("bad usage of SuperJSONOf operator"),1038 Path: mustBe("DATA"),1039 Summary: mustBe("SuperJSONOf() only accepts JSON objects {…}"),1040 })1041 //1042 // Stringification1043 test.EqualStr(t, td.SuperJSONOf(`{}`).String(), `SuperJSONOf({})`)1044 test.EqualStr(t, td.SuperJSONOf(`{"foo":1, "bar":2}`).String(),1045 `1046SuperJSONOf({1047 "bar": 2,1048 "foo": 11049 })`[1:])1050 test.EqualStr(t,1051 td.SuperJSONOf(`{"label": $value, "zip": $^NotZero}`,1052 td.Tag("value", td.Bag(1053 td.SuperJSONOf(`{"name": $1,"age":$2}`,1054 td.HasPrefix("Bob"),1055 td.Between(12, 24),1056 ),1057 td.SuperJSONOf(`{"name": $1}`, td.HasPrefix("Alice")),1058 )),1059 ).String(),1060 `1061SuperJSONOf({1062 "label": "$value" /* Bag(SuperJSONOf({1063 "age": "$2" /* 12 ≤ got ≤ 24 */,1064 "name": "$1" /* HasPrefix("Bob") */1065 }),1066 SuperJSONOf({1067 "name": "$1" /* HasPrefix("Alice") */1068 })) */,1069 "zip": NotZero()1070 })`[1:])1071 // Erroneous op1072 test.EqualStr(t, td.SuperJSONOf(`123`).String(), "SuperJSONOf(<ERROR>)")1073}1074func TestSuperJSONOfTypeBehind(t *testing.T) {1075 equalTypes(t, td.SuperJSONOf(`{"a":12}`), (map[string]any)(nil))1076 // Erroneous op1077 equalTypes(t, td.SuperJSONOf(`123`), nil)1078}...

Full Screen

Full Screen

api_delete_test.go

Source:api_delete_test.go Github

copy

Full Screen

...24 var slug string25 testAPI.PostJSON("/api/v1/shorturls", gin.H{"long_url": "https://www.google.com"}).26 CmpStatus(http.StatusCreated).27 CmpJSONBody(28 td.SuperJSONOf(29 `{"slug": "$slug"}`,30 td.Tag("slug", td.Catch(&slug, td.Ignore())),31 td.Tag("shortUrl", td.Ignore()),32 ),33 )34 testAPI.Delete(fmt.Sprintf("/api/v1/shorturls/%s", slug), nil).35 CmpStatus(http.StatusNoContent)36 testAPI.Get(fmt.Sprintf("/%s", slug)).CmpStatus(http.StatusNotFound)37}38func (suite *deleteSuite) TestDeleteWithInvalidSlugReturns404() {39 t := suite.T()40 testServer := TestContext.server41 testAPI := tdhttp.NewTestAPI(t, testServer)42 testAPI.Delete("/api/v1/shorturls/invalid", nil)....

Full Screen

Full Screen

SuperJSONOf

Using AI Code Generation

copy

Full Screen

1import (2type td struct {3}4func main() {5json := `{"name":"John", "age":30, "cars": {"car1":"Ford", "car2":"BMW", "car3":"Fiat"}}`6name, _ := jsonparser.GetString([]byte(json), "name")7fmt.Println(name)8fmt.Println(gjson.Get(json, "name").String())9json, _ = sjson.Set(json, "name", "Jane")10fmt.Println(json)11td := td{Name: "John", Age: 30}12json = SuperJSONOf(td)13fmt.Println(json)14}15import (16type td struct {17}18func main() {19json := `{"name":"John", "age":30, "cars": {"car1":"Ford", "car2":"BMW", "car3":"Fiat"}}`20name, _ := jsonparser.GetString([]byte(json), "name")21fmt.Println(name)22fmt.Println(gjson.Get(json, "name").String())23json, _ = sjson.Set(json, "name", "Jane")24fmt.Println(json)25td := td{Name: "John", Age: 30}26json = SuperJSONOf(td)27fmt.Println(json)28}29import (

Full Screen

Full Screen

SuperJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 type Person struct {4 }5 p := Person{6 }7 json, _ := superstruct.SuperJSONOf(p)8 fmt.Println(json)9}10import (11func main() {12 type Person struct {13 }14 p := Person{15 }16 json, _ := superstruct.SuperJSONOf(p)17 fmt.Println(json)18}19import (20func main() {21 type Person struct {22 }23 p := Person{24 }25 json, _ := superstruct.SuperJSONOf(p)26 fmt.Println(json)27}28import (29func main() {30 type Person struct {31 }

Full Screen

Full Screen

SuperJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 t := td.New()4 t.Add("a", "b")5 t.Add("c", "d")6 fmt.Println(t.SuperJSONOf("a"))7}8import (9func main() {10 t := td.New()11 t.Add("a", "b")12 t.Add("c", "d")13 fmt.Println(t.SuperJSON())14}15import (16func main() {17 t := td.New()18 t.Add("a", "b")19 t.Add("c", "d")20 fmt.Println(t.SuperJSONOf("c"))21}22import (23func main() {24 t := td.New()25 t.Add("a", "b")26 t.Add("c", "d")27 fmt.Println(t.SuperJSON())28}29import (30func main() {31 t := td.New()32 t.Add("a", "b")33 t.Add("c", "d")34 fmt.Println(t.SuperJSONOf("b"))35}

Full Screen

Full Screen

SuperJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 fmt.Println("Hello, playground")4 tdobj = td.Tdlib{}5 fmt.Println("SuperJSONOf", tdobj.SuperJSONOf("Hello"))6}7import (8func main() {9 fmt.Println("Hello, playground")10 tdobj = td.Tdlib{}11 fmt.Println("SuperJSONOf", tdobj.SuperJSONOf("Hello"))12}13import (14func main() {15 fmt.Println("Hello, playground")16 tdobj = td.Tdlib{}17 fmt.Println("SuperJSONOf", tdobj.SuperJSONOf("Hello"))18}19import (20func main() {21 fmt.Println("Hello, playground")22 tdobj = td.Tdlib{}23 fmt.Println("SuperJSONOf", tdobj.SuperJSONOf("Hello"))24}25import (26func main() {27 fmt.Println("Hello, playground")28 tdobj = td.Tdlib{}29 fmt.Println("SuperJSONOf", tdobj.SuperJSONOf("Hello"))30}31import (32func main() {33 fmt.Println("Hello, playground")34 tdobj = td.Tdlib{}35 fmt.Println("SuperJSONOf", tdobj.SuperJSONOf("Hello"))36}37import (

Full Screen

Full Screen

SuperJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 fmt.Println(td.SuperJSONOf(td.Person{Name: "John", Age: 21}))4}5import (6func main() {7 fmt.Println(td.SuperJSONOf(td.Person{Name: "John", Age: 21}))8}9import (10func main() {11 fmt.Println(td.SuperJSONOf(td.Person{Name: "John", Age: 21}))12}13import (14func main() {15 fmt.Println(td.SuperJSONOf(td.Person{Name: "John", Age: 21}))16}

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run Go-testdeep automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Most used method in

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful