gkampitakis / go-snaps

Jest-like snapshot testing in Golang 📸
https://pkg.go.dev/github.com/gkampitakis/go-snaps
MIT License
146 stars 6 forks source link

[Bug]: Nested arrays keys #84

Open manifestori opened 7 months ago

manifestori commented 7 months ago

Description

When trying to this library with a complex json. for example:

Attempting to match.Any("#.components.#.properties.value") yields a bad snapshot. modifying to match.Any("0.components.#.properties.value") yields a good but incomplete matched snapshot.

A solution for prerendering the matchers based on the input is non-functional. (creating a matcher per nested of nested matcher).

It's probably due to limitations with gjson - https://github.com/tidwall/gjson/issues/267 As you figured, snapshots can be (and probably are) complex, nested arrays within an array is very common, this yields using a snapshot almost useless.

We can try fixing it ourselves, parsing the '.#.' and running through a loop with '.0.' ... '.n.' new matchers to workaround this. wdyt?


[TestGenerateSync/happy:cyclonedx_mono_with_and_go_trivy - 1]
{
 "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
 "bomFormat": "CycloneDX",
 "components": [
  {
   "bom-ref": "<Any value>",
   "name": "go.mod",
   "properties": [
    {
     "name": "aquasecurity:trivy:Class",
     "value": "lang-pkgs"
    },
    {
     "name": "aquasecurity:trivy:Type",
     "value": "gomod"
    }
   ],
   "type": "application"
  },
  {
   "bom-ref": "<Any value>",
   "name": "github.com/davecgh/go-spew",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "github.com/davecgh/go-spew@v1.1.0"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/github.com/davecgh/go-spew@1.1.0",
   "type": "library",
   "version": "1.1.0"
  },
  {
   "bom-ref": "<Any value>",
   "name": "github.com/jmespath/go-jmespath/internal/testify",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "github.com/jmespath/go-jmespath/internal/testify@v1.5.1"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/github.com/jmespath/go-jmespath/internal/testify@1.5.1",
   "type": "library",
   "version": "1.5.1"
  },
  {
   "bom-ref": "<Any value>",
   "licenses": [
    {
     "license": {
      "name": "Apache-2.0"
     }
    }
   ],
   "name": "github.com/jmespath/go-jmespath",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "github.com/jmespath/go-jmespath@v0.4.0"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/github.com/jmespath/go-jmespath@0.4.0",
   "type": "library",
   "version": "0.4.0"
  },
  {
   "bom-ref": "<Any value>",
   "licenses": [
    {
     "license": {
      "name": "BSD-2-Clause"
     }
    }
   ],
   "name": "github.com/pkg/errors",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "github.com/pkg/errors@v0.9.1"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/github.com/pkg/errors@0.9.1",
   "type": "library",
   "version": "0.9.1"
  },
  {
   "bom-ref": "<Any value>",
   "name": "github.com/pmezard/go-difflib",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "github.com/pmezard/go-difflib@v1.0.0"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/github.com/pmezard/go-difflib@1.0.0",
   "type": "library",
   "version": "1.0.0"
  },
  {
   "bom-ref": "<Any value>",
   "name": "github.com/stretchr/objx",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "github.com/stretchr/objx@v0.1.0"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/github.com/stretchr/objx@0.1.0",
   "type": "library",
   "version": "0.1.0"
  },
  {
   "bom-ref": "<Any value>",
   "name": "github.com/yuin/goldmark",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "github.com/yuin/goldmark@v1.4.13"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/github.com/yuin/goldmark@1.4.13",
   "type": "library",
   "version": "1.4.13"
  },
  {
   "bom-ref": "<Any value>",
   "name": "golang.org/x/crypto",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "golang.org/x/crypto@v0.0.0-20210921155107-089bfa567519"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/golang.org/x/crypto@0.0.0-20210921155107-089bfa567519",
   "type": "library",
   "version": "0.0.0-20210921155107-089bfa567519"
  },
  {
   "bom-ref": "<Any value>",
   "name": "golang.org/x/mod",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "golang.org/x/mod@v0.6.0-dev.0.20220419223038-86c51ed26bb4"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/golang.org/x/mod@0.6.0-dev.0.20220419223038-86c51ed26bb4",
   "type": "library",
   "version": "0.6.0-dev.0.20220419223038-86c51ed26bb4"
  },
  {
   "bom-ref": "<Any value>",
   "name": "golang.org/x/net",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "golang.org/x/net@v0.1.0"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/golang.org/x/net@0.1.0",
   "type": "library",
   "version": "0.1.0"
  },
  {
   "bom-ref": "<Any value>",
   "name": "golang.org/x/sync",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "golang.org/x/sync@v0.0.0-20220722155255-886fb9371eb4"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/golang.org/x/sync@0.0.0-20220722155255-886fb9371eb4",
   "type": "library",
   "version": "0.0.0-20220722155255-886fb9371eb4"
  },
  {
   "bom-ref": "<Any value>",
   "name": "golang.org/x/sys",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "golang.org/x/sys@v0.1.0"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/golang.org/x/sys@0.1.0",
   "type": "library",
   "version": "0.1.0"
  },
  {
   "bom-ref": "<Any value>",
   "name": "golang.org/x/term",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "golang.org/x/term@v0.1.0"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/golang.org/x/term@0.1.0",
   "type": "library",
   "version": "0.1.0"
  },
  {
   "bom-ref": "<Any value>",
   "name": "golang.org/x/text",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "golang.org/x/text@v0.4.0"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/golang.org/x/text@0.4.0",
   "type": "library",
   "version": "0.4.0"
  },
  {
   "bom-ref": "<Any value>",
   "name": "golang.org/x/tools",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "golang.org/x/tools@v0.1.12"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/golang.org/x/tools@0.1.12",
   "type": "library",
   "version": "0.1.12"
  },
  {
   "bom-ref": "<Any value>",
   "name": "golang.org/x/xerrors",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "golang.org/x/xerrors@v0.0.0-20190717185122-a985d3407aa7"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/golang.org/x/xerrors@0.0.0-20190717185122-a985d3407aa7",
   "type": "library",
   "version": "0.0.0-20190717185122-a985d3407aa7"
  },
  {
   "bom-ref": "<Any value>",
   "name": "gopkg.in/check.v1",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/gopkg.in/check.v1@0.0.0-20161208181325-20d25e280405",
   "type": "library",
   "version": "0.0.0-20161208181325-20d25e280405"
  },
  {
   "bom-ref": "<Any value>",
   "name": "gopkg.in/yaml.v2",
   "properties": [
    {
     "name": "aquasecurity:trivy:PkgID",
     "value": "gopkg.in/yaml.v2@v2.2.8"
    },
    {
     "name": "aquasecurity:trivy:PkgType",
     "value": "gomod"
    }
   ],
   "purl": "pkg:golang/gopkg.in/yaml.v2@2.2.8",
   "type": "library",
   "version": "2.2.8"
  }
 ],
 "dependencies": [
  {
   "dependsOn": [
    "a0fedf37-f446-4629-ab6c-fbebd72d5034"
   ],
   "ref": "4dacd68b-b238-4db3-a1b1-d388034af718"
  },
  {
   "dependsOn": [
    "pkg:golang/github.com/davecgh/go-spew@1.1.0",
    "pkg:golang/github.com/jmespath/go-jmespath@0.4.0",
    "pkg:golang/github.com/pkg/errors@0.9.1",
    "pkg:golang/github.com/pmezard/go-difflib@1.0.0",
    "pkg:golang/github.com/stretchr/objx@0.1.0",
    "pkg:golang/github.com/yuin/goldmark@1.4.13",
    "pkg:golang/golang.org/x/crypto@0.0.0-20210921155107-089bfa567519",
    "pkg:golang/golang.org/x/mod@0.6.0-dev.0.20220419223038-86c51ed26bb4",
    "pkg:golang/golang.org/x/net@0.1.0",
    "pkg:golang/golang.org/x/sync@0.0.0-20220722155255-886fb9371eb4",
    "pkg:golang/golang.org/x/sys@0.1.0",
    "pkg:golang/golang.org/x/term@0.1.0",
    "pkg:golang/golang.org/x/text@0.4.0",
    "pkg:golang/golang.org/x/tools@0.1.12",
    "pkg:golang/golang.org/x/xerrors@0.0.0-20190717185122-a985d3407aa7",
    "pkg:golang/gopkg.in/check.v1@0.0.0-20161208181325-20d25e280405",
    "pkg:golang/gopkg.in/yaml.v2@2.2.8"
   ],
   "ref": "a0fedf37-f446-4629-ab6c-fbebd72d5034"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/github.com/davecgh/go-spew@1.1.0"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/github.com/jmespath/go-jmespath/internal/testify@1.5.1"
  },
  {
   "dependsOn": [
    "pkg:golang/github.com/jmespath/go-jmespath/internal/testify@1.5.1"
   ],
   "ref": "pkg:golang/github.com/jmespath/go-jmespath@0.4.0"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/github.com/pkg/errors@0.9.1"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/github.com/pmezard/go-difflib@1.0.0"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/github.com/stretchr/objx@0.1.0"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/github.com/yuin/goldmark@1.4.13"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/golang.org/x/crypto@0.0.0-20210921155107-089bfa567519"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/golang.org/x/mod@0.6.0-dev.0.20220419223038-86c51ed26bb4"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/golang.org/x/net@0.1.0"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/golang.org/x/sync@0.0.0-20220722155255-886fb9371eb4"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/golang.org/x/sys@0.1.0"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/golang.org/x/term@0.1.0"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/golang.org/x/text@0.4.0"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/golang.org/x/tools@0.1.12"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/golang.org/x/xerrors@0.0.0-20190717185122-a985d3407aa7"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/gopkg.in/check.v1@0.0.0-20161208181325-20d25e280405"
  },
  {
   "dependsOn": [],
   "ref": "pkg:golang/gopkg.in/yaml.v2@2.2.8"
  }
 ],
 "metadata": {
  "component": {
   "bom-ref": "<Any value>",
   "name": "testdata/aws-sdk-go",
   "properties": [
    {
     "name": "aquasecurity:trivy:SchemaVersion",
     "value": "2"
    }
   ],
   "type": "application"
  },
  "timestamp": "<Any value>",
  "tools": [
   {
    "name": "trivy",
    "vendor": "aquasecurity",
    "version": "0.47.0"
   }
  ]
 },
 "serialNumber": "<Any value>",
 "specVersion": "1.5",
 "version": 1,
 "vulnerabilities": []
}
---

Steps to Reproduce

Run with attached JSON (or simplified version) or any [{ ... , arr: [ { id: 123 } ]}] with a "#.arr.#.id" matcher.

Expected Behavior

No response

gkampitakis commented 7 months ago

Hey 👋 thanks a lot for opening this issue and using the library. I am not super familiar with the more complicated cases of gjson syntax, I have only used the simple examples.

So for me to understand the issue with #.arr.#.id where doesn't iterrate through all the items? does this happen only for double nested? or triple nested? What was your idea for handling this in go-snaps level?

manifestori commented 7 months ago

Hey @gkampitakis it doesn't matter if it's double or triple. If you use # more than once, you get a false positive match and the snapshot will yield just a <Any value> result. (unusable snapshot).

My idea was to parse the path before passing it to gjson. Breaking it down to multiple calls to gjson (instead of 1 call with #.prop.#.id), pass n times 0...n.prop.# to gjson and merge result (recursively)

this should be fixed on Jason, but maybe it's an easier workaround to implement.

gkampitakis commented 6 months ago

Hey 👋 I am not sure how much I want to add this fix in go-snaps 😞