diff --git a/README.md b/README.md index 20f255f..7c07435 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,19 @@ func TestWhatever(t *testing.T) { The above will fail your tests because the `time` key was not present in the actual JSON, and the `uuid` was `null`. +### Ignore ordering in arrays + +If your JSON payload contains an array with elements whose ordering is not deterministic, then you can use the `"<>"` directive as the first element of the array in question: + +```go +func TestUnorderedArray(t *testing.T) { + ja := jsonassert.New(t) + payload := `["bar", "foo", "baz"]` + ja.Assertf(payload, `["foo", "bar", "baz"]`) // Order matters, will fail your test. + ja.Assertf(payload, `["<>", "foo", "bar", "baz"]`) // Order agnostic, will pass your test. +} +``` + ## Docs You can find the [GoDocs for this package here](https://pkg.go.dev/github.com/kinbiko/jsonassert). diff --git a/array.go b/array.go index 0e4b353..b38e1f4 100644 --- a/array.go +++ b/array.go @@ -7,6 +7,71 @@ import ( ) func (a *Asserter) checkArray(path string, act, exp []interface{}) { + a.tt.Helper() + if len(exp) > 0 && exp[0] == "<>" { + a.checkArrayUnordered(path, act, exp[1:]) + } else { + a.checkArrayOrdered(path, act, exp) + } +} + +func (a *Asserter) checkArrayUnordered(path string, act, exp []interface{}) { + a.tt.Helper() + if len(act) != len(exp) { + a.tt.Errorf("length of arrays at '%s' were different. Expected array to be of length %d, but contained %d element(s)", path, len(exp), len(act)) + serializedAct, serializedExp := serialize(act), serialize(exp) + if len(serializedAct+serializedExp) < 50 { + a.tt.Errorf("actual JSON at '%s' was: %+v, but expected JSON was: %+v, potentially in a different order", path, serializedAct, serializedExp) + } else { + a.tt.Errorf("actual JSON at '%s' was:\n%+v\nbut expected JSON was:\n%+v,\npotentially in a different order", path, serializedAct, serializedExp) + } + return + } + + for i, actEl := range act { + found := false + for _, expEl := range exp { + if a.deepEqual(actEl, expEl) { + found = true + } + } + if !found { + serializedEl := serialize(actEl) + if len(serializedEl) < 50 { + a.tt.Errorf("actual JSON at '%s[%d]' contained an unexpected element: %s", path, i, serializedEl) + } else { + a.tt.Errorf("actual JSON at '%s[%d]' contained an unexpected element:\n%s", path, i, serializedEl) + } + } + } + + for i, expEl := range exp { + found := false + for _, actEl := range act { + found = found || a.deepEqual(expEl, actEl) + } + if !found { + serializedEl := serialize(expEl) + if len(serializedEl) < 50 { + a.tt.Errorf("expected JSON at '%s[%d]': %s was missing from actual payload", path, i, serializedEl) + } else { + a.tt.Errorf("expected JSON at '%s[%d]':\n%s\nwas missing from actual payload", path, i, serializedEl) + } + } + } +} + +func (a *Asserter) deepEqual(act, exp interface{}) bool { + // There's a non-zero chance that JSON serialization will *not* be + // deterministic in the future like it is in v1.16. + // However, until this is the case, I can't seem to find a test case that + // makes this evaluation return a false positive. + // The benefit is a lot of simplicity and considerable performance benefits + // for large nested structures. + return serialize(act) == serialize(exp) +} + +func (a *Asserter) checkArrayOrdered(path string, act, exp []interface{}) { a.tt.Helper() if len(act) != len(exp) { a.tt.Errorf("length of arrays at '%s' were different. Expected array to be of length %d, but contained %d element(s)", path, len(exp), len(act)) @@ -28,9 +93,6 @@ func extractArray(s string) ([]interface{}, error) { if len(s) == 0 { return nil, fmt.Errorf("cannot parse empty string as array") } - if s[0] != '[' { - return nil, fmt.Errorf("cannot parse '%s' as array", s) - } var arr []interface{} err := json.Unmarshal([]byte(s), &arr) return arr, err diff --git a/examples_test.go b/examples_test.go index ee081a2..0a33ea0 100644 --- a/examples_test.go +++ b/examples_test.go @@ -44,3 +44,14 @@ func ExampleAsserter_Assertf_presenceOnly() { //unexpected object key(s) ["hi"] found at '$' //expected object key(s) ["hello"] missing at '$' } + +func ExampleAsserter_Assertf_unorderedArray() { + ja := jsonassert.New(t) + ja.Assertf( + `["zero", "one", "two"]`, + `["<>", "one", "two", "three"]`, + ) + //output: + //actual JSON at '$[0]' contained an unexpected element: "zero" + //expected JSON at '$[2]': "three" was missing from actual payload +} diff --git a/exports.go b/exports.go index 1e2fac6..eee778f 100644 --- a/exports.go +++ b/exports.go @@ -20,14 +20,23 @@ along with the "world" format argument. For example: ja.Assertf(`{"hello": "world"}`, `{"hello":"%s"}`, "world") -Additionally, you may wish to make assertions against the *presence* of a -value, but not against its value. For example: +You may wish to make assertions against the *presence* of a value, but not +against its value. For example: ja.Assertf(`{"uuid": "94ae1a31-63b2-4a55-a478-47764b60c56b"}`, `{"uuid":"<>"}`) will verify that the UUID field is present, but does not check its actual value. You may use "<>" against any type of value. The only exception is null, which will result in an assertion failure. + +If you don't know / care about the order of the elements in an array in your +payload, you can ignore the ordering: + + payload := `["bar", "foo", "baz"]` + ja.Assertf(payload, `["<>", "foo", "bar", "baz"]`) + +The above will verify that "foo", "bar", and "baz" are exactly the elements in +the payload, but will ignore the order in which they appear. */ package jsonassert @@ -97,14 +106,23 @@ format-directive. ja.Assertf(`{"averageTestScore": "99%"}`, `{"averageTestScore":"%s"}`, "99%") -Additionally, you may wish to make assertions against the *presence* of a -value, but not against its value. For example: +You may wish to make assertions against the *presence* of a value, but not +against its value. For example: ja.Assertf(`{"uuid": "94ae1a31-63b2-4a55-a478-47764b60c56b"}`, `{"uuid":"<>"}`) will verify that the UUID field is present, but does not check its actual value. You may use "<>" against any type of value. The only exception is null, which will result in an assertion failure. + +If you don't know / care about the order of the elements in an array in your +payload, you can ignore the ordering: + + payload := `["bar", "foo", "baz"]` + ja.Assertf(payload, `["<>", "foo", "bar", "baz"]`) + +The above will verify that "foo", "bar", and "baz" are exactly the elements in +the payload, but will ignore the order in which they appear. */ func (a *Asserter) Assertf(actualJSON, expectedJSON string, fmtArgs ...interface{}) { a.tt.Helper() diff --git a/integration_test.go b/integration_test.go index 61e96c6..732ad56 100644 --- a/integration_test.go +++ b/integration_test.go @@ -2,6 +2,7 @@ package jsonassert_test import ( "fmt" + "io/ioutil" "testing" "github.com/kinbiko/jsonassert" @@ -231,16 +232,192 @@ but expected JSON was: t.Run(name, func(t *testing.T) { tc.check(t) }) } }) + + t.Run("with UNORDERED directive", func(t *testing.T) { + for name, tc := range map[string]*testCase{ + "no elements": {`[]`, `["<>"]`, nil}, + "only one equal element": {`["foo"]`, `["<>", "foo"]`, nil}, + "two elements ordered": { + `["foo", "bar"]`, + `["<>", "foo", "bar"]`, + nil, + }, + "two elements unordered": { + `["bar", "foo"]`, + `["<>", "foo", "bar"]`, + nil, + }, + "different number of elements": { + `["foo"]`, + `["<>", "foo", "bar"]`, + []string{ + `length of arrays at '$' were different. Expected array to be of length 2, but contained 1 element(s)`, + `actual JSON at '$' was: ["foo"], but expected JSON was: ["foo","bar"], potentially in a different order`, + }, + }, + "two different elements": { + `["far", "boo"]`, + `["<>", "foo", "bar"]`, + []string{ + `actual JSON at '$[0]' contained an unexpected element: "far"`, + `actual JSON at '$[1]' contained an unexpected element: "boo"`, + `expected JSON at '$[0]': "foo" was missing from actual payload`, + `expected JSON at '$[1]': "bar" was missing from actual payload`, + }, + }, + "valid array of different primitive types": { + `["far", 1, null, true, [], {}]`, + `["<>", true, 1, null, [], "far", {} ]`, + nil, + }, + "duplicates should still error out": { + `["foo", "boo", "foo"]`, + `["<>", "foo", "boo"]`, + []string{ + `length of arrays at '$' were different. Expected array to be of length 2, but contained 3 element(s)`, + `actual JSON at '$' was: ["foo","boo","foo"], but expected JSON was: ["foo","boo"], potentially in a different order`, + }, + }, + "nested unordered arrays": { + // really long object means that serializing it the same is + // highly unlikely should the determinisim of JSON + // serialization go away. + `[{"20": 20}, {"19": 19}, {"18": 18 }, {"17": 17 }, {"16": 16 }, {"15": 15 }, {"14": 14 }, {"13": 13 }, {"12": 12 }, {"11": 11 }, {"10": 10 }, {"9": 9 }, {"8": 8 }, {"7": 7 }, {"6": 6 }, {"5": 5 }, {"4": 4 }, {"3": 3 }, {"2": 2 }, {"1": 1}]`, + `["<>", {"1": 1}, {"2": 2}, {"3": 3}, {"4": 4}, {"5": 5}, {"6": 6}, {"7": 7}, {"8": 8}, {"9": 9}, {"10": 10}, {"11": 11}, {"12": 12}, {"13": 13}, {"14": 14}, {"15": 15}, {"16": 16}, {"17": 17}, {"18": 18}, {"19": 19}, {"20": 20}]`, + nil, + }, + } { + t.Run(name, func(t *testing.T) { tc.check(t) }) + } + }) }) t.Run("extra long strings should be formatted on a new line", func(t *testing.T) { - tc := &testCase{ - `"lorem ipsum dolor sit amet lorem ipsum dolor sit amet"`, - `"lorem ipsum dolor sit amet lorem ipsum dolor sit amet why do I have to be the test string?"`, - []string{`expected string at '$' to be + for name, tc := range map[string]*testCase{ + "simple test string": { + `"lorem ipsum dolor sit amet lorem ipsum dolor sit amet"`, + `"lorem ipsum dolor sit amet lorem ipsum dolor sit amet why do I have to be the test string?"`, + []string{`expected string at '$' to be 'lorem ipsum dolor sit amet lorem ipsum dolor sit amet why do I have to be the test string?' but was -'lorem ipsum dolor sit amet lorem ipsum dolor sit amet'`}} +'lorem ipsum dolor sit amet lorem ipsum dolor sit amet'`, + }, + }, + "nested unordered arrays": { + `["lorem ipsum dolor sit amet lorem ipsum dolor sit amet", "lorem ipsum dolor sit amet lorem ipsum dolor sit amet"]`, + `["<>", "lorem ipsum dolor sit amet lorem ipsum dolor sit amet why do I have to be the test string?"]`, + []string{ + `length of arrays at '$' were different. Expected array to be of length 1, but contained 2 element(s)`, + `actual JSON at '$' was: +["lorem ipsum dolor sit amet lorem ipsum dolor sit amet","lorem ipsum dolor sit amet lorem ipsum dolor sit amet"] +but expected JSON was: +["lorem ipsum dolor sit amet lorem ipsum dolor sit amet why do I have to be the test string?"], +potentially in a different order`, + }, + }, + } { + t.Run(name, func(t *testing.T) { tc.check(t) }) + } + }) + + t.Run("big fat test", func(t *testing.T) { + var ( + bigFatPayloadActual, _ = ioutil.ReadFile("testdata/big-fat-payload-actual.json") + bigFatPayloadExpected, _ = ioutil.ReadFile("testdata/big-fat-payload-expected.json") + ) + + tc := testCase{ + act: fmt.Sprintf(`{ + "null": null, + "emptyObject": {}, + "emptyArray": [], + "emptyString": "", + "zero": 0, + "boolean": false, + "positiveInt": 125, + "negativeInt": -1245, + "positiveFloats": 12.45, + "negativeFloats": -12.345, + "strings": "hello 世界", + "flatArray": ["foo", "bar", "baz"], + "flatObject": {"boo": "far", "biz": "qwerboipqwerb"}, + "nestedArray": ["boop", ["poob", {"bat": "boi", "asdf": 14, "oi": ["boy"]}], {"n": null}], + "nestedObject": %s + }`, string(bigFatPayloadActual)), + exp: fmt.Sprintf(`{ + "nil": null, + "emptyObject": [], + "emptyArray": [null], + "emptyString": " ", + "zero": 0.00001, + "boolean": true, + "positiveInt": 124, + "negativeInt": -1246, + "positiveFloats": 11.45, + "negativeFloats": -13.345, + "strings": "hello world", + "flatArray": ["fo", "ar", "baz"], + "flatObject": {"bo": "far", "biz": "qwerboipqwer"}, + "nestedArray": ["oop", ["pob", {"bat": "oi", "asdf": 13, "oi": ["by"]}], {"m": null}], + "nestedObject": %s + }`, string(bigFatPayloadExpected)), + msgs: []string{ + `unexpected object key(s) ["null"] found at '$'`, + `expected object key(s) ["nil"] missing at '$'`, + + `actual JSON (object) and expected JSON (array) were of different types at '$.emptyObject'`, + + `length of arrays at '$.emptyArray' were different. Expected array to be of length 1, but contained 0 element(s)`, + `actual JSON at '$.emptyArray' was: [], but expected JSON was: [null]`, + + `expected string at '$.emptyString' to be ' ' but was ''`, + + `expected number at '$.zero' to be '0.0000100' but was '0.0000000'`, + + `expected boolean at '$.boolean' to be true but was false`, + + `expected number at '$.positiveInt' to be '124.0000000' but was '125.0000000'`, + + `expected number at '$.negativeInt' to be '-1246.0000000' but was '-1245.0000000'`, + + `expected number at '$.positiveFloats' to be '11.4500000' but was '12.4500000'`, + + `expected number at '$.negativeFloats' to be '-13.3450000' but was '-12.3450000'`, + + `expected string at '$.strings' to be 'hello world' but was 'hello 世界'`, + + `expected string at '$.flatArray[0]' to be 'fo' but was 'foo'`, + `expected string at '$.flatArray[1]' to be 'ar' but was 'bar'`, + + `unexpected object key(s) ["boo"] found at '$.flatObject'`, + `expected object key(s) ["bo"] missing at '$.flatObject'`, + `expected string at '$.flatObject.biz' to be 'qwerboipqwer' but was 'qwerboipqwerb'`, + + `expected string at '$.nestedArray[0]' to be 'oop' but was 'boop'`, + `expected string at '$.nestedArray[1][0]' to be 'pob' but was 'poob'`, + `expected number at '$.nestedArray[1][1].asdf' to be '13.0000000' but was '14.0000000'`, + `expected string at '$.nestedArray[1][1].bat' to be 'oi' but was 'boi'`, + `expected string at '$.nestedArray[1][1].oi[0]' to be 'by' but was 'boy'`, + `unexpected object key(s) ["n"] found at '$.nestedArray[2]'`, + `expected object key(s) ["m"] missing at '$.nestedArray[2]'`, + + `expected boolean at '$.nestedObject.is_full_report' to be false but was true`, + `expected string at '$.nestedObject.id' to be 's869n10s9000060596qs3007' but was 's869n10s9000060s96qs3007'`, + `actual JSON (object) and expected JSON (null) were of different types at '$.nestedObject.request.headers'`, + `expected string at '$.nestedObject.metaData.device.userAgent' to be +'Mozilla/4.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' +but was +'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'`, + `expected 7 keys at '$.nestedObject.source_map_failure' but got 8 keys`, + `unexpected object key(s) ["source_map_url"] found at '$.nestedObject.source_map_failure'`, + `expected boolean at '$.nestedObject.source_map_failure.has_uploaded_source_maps_for_version' to be true but was false`, + `actual JSON at '$.nestedObject.breadcrumbs[1]' contained an unexpected element: +"Something that is most definitely missing from the expected one, right??"`, + `expected JSON at '$.nestedObject.breadcrumbs[1]': +"Something that is most definitely missing from the actual one, right??" +was missing from actual payload`, + }, + } tc.check(t) }) } @@ -253,26 +430,28 @@ type testCase struct { func (tc *testCase) check(t *testing.T) { tp := &testPrinter{} jsonassert.New(tp).Assertf(tc.act, tc.exp) + if got := len(tp.messages); got != len(tc.msgs) { t.Errorf("expected %d assertion message(s) but got %d", len(tc.msgs), got) - if len(tc.msgs) > 0 { - t.Errorf("Expected the following messages:") - for _, msg := range tc.msgs { - t.Errorf(" - %s", msg) - } - } + } - if len(tp.messages) > 0 { - t.Errorf("Got the following messages:") - for _, msg := range tp.messages { - t.Errorf(" - %s", msg) - } + for _, expMsg := range tc.msgs { + found := false + for _, printedMsg := range tp.messages { + found = found || expMsg == printedMsg + } + if !found { + t.Errorf("missing assertion message:\n%s", expMsg) } - return } - for i := range tc.msgs { - if exp, got := tc.msgs[i], tp.messages[i]; got != exp { - t.Errorf("expected assertion message:\n'%s'\nbut got\n'%s'", exp, got) + + for _, printedMsg := range tp.messages { + found := false + for _, expMsg := range tc.msgs { + found = found || printedMsg == expMsg + } + if !found { + t.Errorf("unexpected assertion message:\n%s", printedMsg) } } } diff --git a/testdata/big-fat-payload-actual.json b/testdata/big-fat-payload-actual.json new file mode 100644 index 0000000..f597f74 --- /dev/null +++ b/testdata/big-fat-payload-actual.json @@ -0,0 +1,412 @@ +{ + "id": "s869n10s9000060s96qs3007", + "is_full_report": true, + "error_id": "60f96df393459c000789707e", + "received_at": "2021-07-22T13:09:07.029Z", + "exceptions": [ + { + "error_class": "ReferenceError", + "message": "setLoggedOut is not defined", + "type": "browserjs", + "stacktrace": [ + { + "column_number": 19, + "in_project": null, + "line_number": 349, + "method": "App", + "file": "http://localhost:3000/static/js/main.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 22, + "in_project": null, + "line_number": 24476, + "method": "renderWithHooks", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 17, + "in_project": null, + "line_number": 27087, + "method": "mountIndeterminateComponent", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 20, + "in_project": null, + "line_number": 28166, + "method": "beginWork", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 18, + "in_project": null, + "line_number": 9869, + "method": "HTMLUnknownElement.callCallback", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 20, + "in_project": null, + "line_number": 9918, + "method": "Object.invokeGuardedCallbackDev", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 35, + "in_project": null, + "line_number": 9971, + "method": "invokeGuardedCallback", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 11, + "in_project": null, + "line_number": 32732, + "method": "beginWork$1", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": "1.chunk.js", + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 16, + "in_project": null, + "line_number": 31696, + "method": "performUnitOfWork", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 26, + "in_project": null, + "line_number": 31672, + "method": "workLoopSync", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 13, + "in_project": null, + "line_number": 31290, + "method": "performSyncWorkOnRoot", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 11, + "in_project": null, + "line_number": 30722, + "method": "scheduleUpdateOnFiber", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 7, + "in_project": null, + "line_number": 33871, + "method": "updateContainer", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 11, + "in_project": null, + "line_number": 34254, + "method": "", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 16, + "in_project": null, + "line_number": 31440, + "method": "unbatchedUpdates", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 9, + "in_project": null, + "line_number": 34253, + "method": "legacyRenderSubtreeIntoContainer", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 14, + "in_project": null, + "line_number": 34336, + "method": "Object.render", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 50, + "in_project": null, + "line_number": 1075, + "method": "Module../src/index.tsx", + "file": "http://localhost:3000/static/js/main.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 30, + "in_project": null, + "line_number": 785, + "method": "__webpack_require__", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 20, + "in_project": null, + "line_number": 151, + "method": "fn", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 18, + "in_project": null, + "line_number": 1088, + "method": "Object.1", + "file": "http://localhost:3000/static/js/main.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 30, + "in_project": null, + "line_number": 785, + "method": "__webpack_require__", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 23, + "in_project": null, + "line_number": 46, + "method": "checkDeferredModules", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 19, + "in_project": null, + "line_number": 33, + "method": "Array.webpackJsonpCallback [as push]", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 59, + "in_project": null, + "line_number": 1, + "method": "", + "file": "http://localhost:3000/static/js/main.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + } + ], + "registers": null + } + ], + "threads": null, + "metaData": { + "device": { + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + } + }, + "request": { + "url": "http://localhost:3000/", + "clientIp": "27.143.62.164", + "headers": {} + }, + "app": { "releaseStage": "development", "duration": 98 }, + "device": { + "osName": "Mac OS X 10.15", + "browserName": "Chrome", + "browserVersion": "91.0.4472", + "orientation": "landscape-primary", + "locale": "en-GB", + "time": "2021-07-22T13:09:06.555Z" + }, + "user": { "id": "27.143.62.164" }, + "breadcrumbs": [ + { + "timestamp": "2021-07-22T13:09:06.526Z", + "name": "Bugsnag loaded", + "type": "navigation", + "metaData": {} + }, + "Something that is most definitely missing from the expected one, right??" + ], + "context": "/", + "severity": "error", + "unhandled": true, + "incomplete": false, + "overridden_severity": null, + "severity_reason": { "type": "handledException" }, + "source_map_failure": { + "reason": "missing-js", + "has_uploaded_source_maps_for_project": false, + "has_uploaded_source_maps_for_version": false, + "is_local_minified_url": true, + "source_map_url": null, + "file_url": "http://localhost:3000/static/js/main.chunk.js", + "platform": null, + "release_variant": null + } +} diff --git a/testdata/big-fat-payload-expected.json b/testdata/big-fat-payload-expected.json new file mode 100644 index 0000000..f6c50aa --- /dev/null +++ b/testdata/big-fat-payload-expected.json @@ -0,0 +1,413 @@ +{ + "id": "s869n10s9000060596qs3007", + "is_full_report": false, + "error_id": "60f96df393459c000789707e", + "received_at": "2021-07-22T13:09:07.029Z", + "exceptions": [ + { + "error_class": "ReferenceError", + "message": "setLoggedOut is not defined", + "type": "browserjs", + "stacktrace": [ + "<>", + { + "column_number": 19, + "in_project": null, + "line_number": 349, + "method": "App", + "file": "http://localhost:3000/static/js/main.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 22, + "in_project": null, + "line_number": 24476, + "method": "renderWithHooks", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 20, + "in_project": null, + "line_number": 28166, + "method": "beginWork", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 18, + "in_project": null, + "line_number": 9869, + "method": "HTMLUnknownElement.callCallback", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 17, + "in_project": null, + "line_number": 27087, + "method": "mountIndeterminateComponent", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 20, + "in_project": null, + "line_number": 9918, + "method": "Object.invokeGuardedCallbackDev", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 35, + "in_project": null, + "line_number": 9971, + "method": "invokeGuardedCallback", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 11, + "in_project": null, + "line_number": 32732, + "method": "beginWork$1", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": "1.chunk.js", + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 16, + "in_project": null, + "line_number": 31696, + "method": "performUnitOfWork", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 26, + "in_project": null, + "line_number": 31672, + "method": "workLoopSync", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 13, + "in_project": null, + "line_number": 31290, + "method": "performSyncWorkOnRoot", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 11, + "in_project": null, + "line_number": 30722, + "method": "scheduleUpdateOnFiber", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 7, + "in_project": null, + "line_number": 33871, + "method": "updateContainer", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 11, + "in_project": null, + "line_number": 34254, + "method": "", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 16, + "in_project": null, + "line_number": 31440, + "method": "unbatchedUpdates", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 9, + "in_project": null, + "line_number": 34253, + "method": "legacyRenderSubtreeIntoContainer", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 14, + "in_project": null, + "line_number": 34336, + "method": "Object.render", + "file": "http://localhost:3000/static/js/1.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 50, + "in_project": null, + "line_number": 1075, + "method": "Module../src/index.tsx", + "file": "http://localhost:3000/static/js/main.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 30, + "in_project": null, + "line_number": 785, + "method": "__webpack_require__", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 20, + "in_project": null, + "line_number": 151, + "method": "fn", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 18, + "in_project": null, + "line_number": 1088, + "method": "Object.1", + "file": "http://localhost:3000/static/js/main.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 30, + "in_project": null, + "line_number": 785, + "method": "__webpack_require__", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 23, + "in_project": null, + "line_number": 46, + "method": "checkDeferredModules", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 19, + "in_project": null, + "line_number": 33, + "method": "Array.webpackJsonpCallback [as push]", + "file": "http://localhost:3000/static/js/bundle.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + }, + { + "column_number": 59, + "in_project": null, + "line_number": 1, + "method": "", + "file": "http://localhost:3000/static/js/main.chunk.js", + "type": null, + "code": null, + "code_file": null, + "address_offset": null, + "macho_uuid": null, + "source_control_link": null, + "source_control_name": "" + } + ], + "registers": null + } + ], + "threads": null, + "metaData": { + "device": { + "userAgent": "Mozilla/4.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + } + }, + "request": { + "url": "http://localhost:3000/", + "clientIp": "27.143.62.164", + "headers": null + }, + "app": { "releaseStage": "development", "duration": 98 }, + "device": { + "osName": "Mac OS X 10.15", + "browserName": "Chrome", + "browserVersion": "91.0.4472", + "orientation": "landscape-primary", + "locale": "en-GB", + "time": "2021-07-22T13:09:06.555Z" + }, + "user": { "id": "27.143.62.164" }, + "breadcrumbs": [ + "<>", + { + "timestamp": "2021-07-22T13:09:06.526Z", + "name": "Bugsnag loaded", + "type": "navigation", + "metaData": {} + }, + "Something that is most definitely missing from the actual one, right??" + ], + "context": "/", + "severity": "error", + "unhandled": true, + "incomplete": false, + "overridden_severity": null, + "severity_reason": { "type": "handledException" }, + "source_map_failure": { + "reason": "missing-js", + "has_uploaded_source_maps_for_project": false, + "has_uploaded_source_maps_for_version": true, + "is_local_minified_url": true, + "file_url": "http://localhost:3000/static/js/main.chunk.js", + "platform": null, + "release_variant": null + } +}