github.com/snyk/vervet/v6@v6.2.4/collator_test.go (about)

     1  package vervet_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	qt "github.com/frankban/quicktest"
     7  
     8  	"github.com/snyk/vervet/v6"
     9  	"github.com/snyk/vervet/v6/testdata"
    10  )
    11  
    12  func TestRefRemover(t *testing.T) {
    13  	c := qt.New(t)
    14  	doc, err := vervet.NewDocumentFile(testdata.Path("resources/projects/2021-08-20/spec.yaml"))
    15  	c.Assert(err, qt.IsNil)
    16  	resp400 := doc.Paths["/orgs/{org_id}/projects/{project_id}"].Delete.Responses["400"]
    17  	errDoc := resp400.Value.Content["application/vnd.api+json"].Schema
    18  	c.Assert(err, qt.IsNil)
    19  	c.Assert("{\"$ref\":\"../errors.yaml#/ErrorDocument\"}", qt.JSONEquals, errDoc)
    20  	in := vervet.NewRefRemover(errDoc)
    21  	err = in.RemoveRef()
    22  	c.Assert(err, qt.IsNil)
    23  	c.Assert(err, qt.IsNil)
    24  	//nolint:lll // acked
    25  	c.Assert("{\"additionalProperties\":false,\"example\":{\"errors\":[{\"detail\":\"Permission denied for this "+
    26  		"resource\",\"status\":\"403\"}],\"jsonapi\":{\"version\":\"1.0\"}},\"properties\":{\"errors\":{\"example\":"+
    27  		"[{\"detail\":\"Permission denied for this resource\",\"status\":\"403\"}],\"items\":{\"additionalProperties\""+
    28  		":false,\"example\":{\"detail\":\"Not Found\",\"status\":\"404\"},\"properties\":{\"detail\":{\"description\":"+
    29  		"\"A human-readable explanation specific to this occurrence of the problem.\",\"example\":\"The request was "+
    30  		"missing these required fields: ...\",\"type\":\"string\"},\"id\":{\"description\":\"A unique identifier for "+
    31  		"this particular occurrence of the problem.\",\"example\":\"f16c31b5-6129-4571-add8-d589da9be524\",\"format\""+
    32  		":\"uuid\",\"type\":\"string\"},\"meta\":{\"additionalProperties\":true,\"example\":{\"key\":\"value\"},\"type\""+
    33  		":\"object\"},\"source\":{\"additionalProperties\":false,\"example\":{\"pointer\":\"/data/attributes\"},\"properties\""+
    34  		":{\"parameter\":{\"description\":\"A string indicating which URI query parameter caused the error.\",\"example\""+
    35  		":\"param1\",\"type\":\"string\"},\"pointer\":{\"description\":\"A JSON Pointer [RFC6901] to the associated entity "+
    36  		"in the request document.\",\"example\":\"/data/attributes\",\"type\":\"string\"}},\"type\":\"object\"},\"status\""+
    37  		":{\"description\":\"The HTTP status code applicable to this problem, expressed as a string value.\",\"example\""+
    38  		":\"400\",\"pattern\":\"^[45]\\\\d\\\\d$\",\"type\":\"string\"}},\"required\":[\"status\",\"detail\"],\"type\""+
    39  		":\"object\"},\"minItems\":1,\"type\":\"array\"},\"jsonapi\":{\"additionalProperties\":false,\"example\":"+
    40  		"{\"version\":\"1.0\"},\"properties\":{\"version\":{\"description\":\"Version of the JSON API specification "+
    41  		"this server supports.\",\"example\":\"1.0\",\"pattern\":\"^(0|[1-9]\\\\d*)\\\\.(0|[1-9]\\\\d*)$\",\"type\""+
    42  		":\"string\"}},\"required\":[\"version\"],\"type\":\"object\"}},\"required\":[\"jsonapi\",\"errors\"],\"type\""+
    43  		":\"object\"}\n", qt.JSONEquals, errDoc)
    44  }
    45  
    46  func TestCollator(t *testing.T) {
    47  	c := qt.New(t)
    48  	collator := vervet.NewCollator()
    49  	projects, err := vervet.LoadResourceVersions(testdata.Path("conflict-components/projects"))
    50  	c.Assert(err, qt.IsNil)
    51  	projectV, err := projects.At("2021-06-04~experimental")
    52  	c.Assert(err, qt.IsNil)
    53  	examples, err := vervet.LoadResourceVersions(testdata.Path("conflict-components/_examples"))
    54  	c.Assert(err, qt.IsNil)
    55  	examplesV, err := examples.At("2021-06-01~experimental")
    56  	c.Assert(err, qt.IsNil)
    57  
    58  	err = collator.Collate(projectV)
    59  	c.Assert(err, qt.IsNil)
    60  	err = collator.Collate(examplesV)
    61  	c.Assert(err, qt.IsNil)
    62  
    63  	result := collator.Result()
    64  	c.Assert(
    65  		result.Paths["/orgs/{orgId}/projects"].
    66  			Get.Responses["200"].
    67  			Value.
    68  			Content["application/vnd.api+json"].
    69  			Schema.Value.Properties["jsonapi"].Ref,
    70  		qt.Equals,
    71  		"#/components/schemas/JsonApi",
    72  	)
    73  	schemaRef := result.
    74  		Paths["/examples/hello-world/{id}"].
    75  		Get.
    76  		Responses["200"].
    77  		Value.
    78  		Content["application/vnd.api+json"].
    79  		Schema.
    80  		Value.
    81  		Properties["jsonapi"]
    82  	c.Assert(schemaRef.Ref, qt.Equals, "")
    83  	c.Assert("{\"additionalProperties\":false,\"example\":{\"version\":\"1.0\"},\"properties\":{\"version\":"+
    84  		"{\"description\":\"Version of the JSON API specification this server supports.\",\"example\":\"1.0\","+
    85  		"\"pattern\":\"^(0|[1-9]\\\\d*)\\\\.(0|[1-9]\\\\d*)$\",\"type\":\"string\"}},\"required\":[\"version\"],\"type\""+
    86  		":\"object\"}\n", qt.JSONEquals, schemaRef.Value)
    87  	c.Assert(result.Components.Schemas["JsonApi"], qt.IsNotNil)
    88  
    89  	projectParameterRef := result.Paths["/orgs/{orgId}/projects"].Get.Parameters[0]
    90  	c.Assert(projectParameterRef.Ref, qt.Equals, "#/components/parameters/Version")
    91  	exampleParameterRef := result.Paths["/examples/hello-world/{id}"].Get.Parameters[0]
    92  	c.Assert(exampleParameterRef.Ref, qt.Equals, "")
    93  	//nolint:lll // acked
    94  	c.Assert("{\"description\":\"The requested version of the endpoint to process the request\",\"example\""+
    95  		":\"2021-06-04\",\"in\":\"query\",\"name\":\"version\",\"required\":true,\"schema\":{\"description\":"+
    96  		"\"Requested API version\",\"pattern\":\"^(wip|work-in-progress|experimental|beta|((([0-9]{4})-([0-1][0-9]))"+
    97  		"-((3[01])|(0[1-9])|([12][0-9]))(~(wip|work-in-progress|experimental|beta))?))$\",\"type\":\"string\"}}\n", qt.JSONEquals, exampleParameterRef.Value)
    98  
    99  	projectConflictRef := result.Paths["/orgs/{orgId}/projects"].Get.Parameters[6]
   100  	exampleConflictRef := result.Paths["/examples/hello-world/{id}"].Get.Parameters[3]
   101  	c.Assert(projectConflictRef.Ref, qt.Not(qt.Equals), exampleConflictRef.Ref)
   102  
   103  	projectResp400Ref := result.Paths["/orgs/{orgId}/projects"].Get.Responses["400"]
   104  	c.Assert(projectResp400Ref.Ref, qt.Equals, "#/components/responses/400")
   105  	exampleResp400Ref := result.Paths["/examples/hello-world/{id}"].Get.Responses["400"]
   106  	c.Assert(exampleResp400Ref.Ref, qt.Equals, "")
   107  	c.Assert("{\"content\":{\"application/vnd.api+json\":{\"schema\":{\"additionalProperties\":false,\"example\":{"+
   108  		"\"errors\":[{\"detail\":\"Permission denied for this resource\",\"status\":\"403\"}],\"jsonapi\":{\"version\":"+
   109  		"\"1.0\"}},\"properties\":{\"errors\":{\"example\":[{\"detail\":\"Permission denied for this resource\",\"status"+
   110  		"\":\"403\"}],\"items\":{\"additionalProperties\":false,\"example\":{\"detail\":\"Not Found\",\"status\":\"404\"}"+
   111  		",\"properties\":{\"detail\":{\"description\":\"A human-readable explanation specific to this occurrence of the "+
   112  		"problem.\",\"example\":\"The request was missing these required fields: ...\",\"type\":\"string\"},\"id\":"+
   113  		"{\"description\":\"A unique identifier for this particular occurrence of the problem.\",\"example\":"+
   114  		"\"f16c31b5-6129-4571-add8-d589da9be524\",\"format\":\"uuid\",\"type\":\"string\"},\"meta\":"+
   115  		"{\"additionalProperties\":true,\"example\":{\"key\":\"value\"},\"type\":\"object\"},\"source\":"+
   116  		"{\"additionalProperties\":false,\"example\":{\"pointer\":\"/data/attributes\"},\"properties\":"+
   117  		"{\"parameter\":{\"description\":\"A string indicating which URI query parameter caused the error."+
   118  		"\",\"example\":\"param1\",\"type\":\"string\"},\"pointer\":{\"description\":\"A JSON Pointer [RFC6901] to the "+
   119  		"associated entity in the request document.\",\"example\":\"/data/attributes\",\"type\":\"string\"}},\"type\":"+
   120  		"\"object\"},\"status\":{\"description\":\"The HTTP status code applicable to this problem, expressed as a "+
   121  		"string value.\",\"example\":\"400\",\"pattern\":\"^[45]\\\\d\\\\d$\",\"type\":\"string\"}},\"required\":"+
   122  		"[\"status\",\"detail\"],\"type\":\"object\"},\"minItems\":1,\"type\":\"array\"},\"jsonapi\":"+
   123  		"{\"additionalProperties\":false,\"example\":{\"version\":\"1.0\"},\"properties\":{\"version\":"+
   124  		"{\"description\":\"Version of the JSON API specification this server supports.\",\"example\":\"1.0\","+
   125  		"\"pattern\":\"^(0|[1-9]\\\\d*)\\\\.(0|[1-9]\\\\d*)$\",\"type\":\"string\"}},\"required\":[\"version\"],\"type\""+
   126  		":\"object\"}},\"required\":[\"jsonapi\",\"errors\"],\"type\":\"object\"}}},\"description\":\"Bad Request: A "+
   127  		"parameter provided as a part of the request was invalid.\",\"headers\":{\"deprecation\":{\"description\":\""+
   128  		"A header containing the deprecation date of the underlying endpoint. For more information, please refer to "+
   129  		"the deprecation header RFC:\\nhttps://tools.ietf.org/id/draft-dalal-deprecation-header-01.html\\n\",\"example"+
   130  		"\":\"2021-07-01T00:00:00Z\",\"schema\":{\"format\":\"date-time\",\"type\":\"string\"}},\"snyk-request-id\":"+
   131  		"{\"description\":\"A header containing a unique id used for tracking this request. If you are reporting an "+
   132  		"issue to Snyk it's very helpful to provide this ID.\\n\",\"example\":\"4b58e274-ec62-4fab-917b-1d2c48d6bdef\""+
   133  		",\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}},\"snyk-version-lifecycle-stage\":{\"description\":"+
   134  		"\"A header containing the version stage of the endpoint. This stage describes the guarantees snyk provides "+
   135  		"surrounding stability of the endpoint.\\n\",\"schema\":{\"enum\":[\"wip\",\"experimental\",\"beta\",\"ga\","+
   136  		"\"deprecated\",\"sunset\"],\"example\":\"ga\",\"type\":\"string\"}},\"snyk-version-requested\":{\"description\""+
   137  		":\"A header containing the version of the endpoint requested by the caller.\",\"example\":\"2021-06-04\",\""+
   138  		"schema\":{\"description\":\"Requested API version\",\"pattern\":\"^(wip|work-in-progress|experimental|beta|"+
   139  		"((([0-9]{4})-([0-1][0-9]))-((3[01])|(0[1-9])|([12][0-9]))(~(wip|work-in-progress|experimental|beta))?))$\""+
   140  		",\"type\":\"string\"}},\"snyk-version-served\":{\"description\":\"A header containing the version of the "+
   141  		"endpoint that was served by the API.\",\"example\":\"2021-06-04\",\"schema\":{\"description\":\"Resolved API "+
   142  		"version\",\"pattern\":\"^((([0-9]{4})-([0-1][0-9]))-((3[01])|(0[1-9])|([12][0-9]))(~"+
   143  		"(wip|work-in-progress|experimental|beta))?)$\",\"type\":\"string\"}},\"sunset\":{\"description\":"+
   144  		"\"A header containing the date of when the underlying endpoint will be removed. This header is only present if "+
   145  		"the endpoint has been deprecated. Please refer to the RFC for more information:"+
   146  		"\\nhttps://datatracker.ietf.org/doc/html/rfc8594\\n\",\"example\":\"2021-08-02T00:00:00Z\",\"schema\":"+
   147  		"{\"format\":\"date-time\",\"type\":\"string\"}}}}\n", qt.JSONEquals, exampleResp400Ref.Value)
   148  }
   149  
   150  func TestCollateUseFirstRoute(t *testing.T) {
   151  	c := qt.New(t)
   152  	collator := vervet.NewCollator(vervet.UseFirstRoute(true))
   153  	examples1, err := vervet.LoadResourceVersions(testdata.Path("conflict/_examples"))
   154  	c.Assert(err, qt.IsNil)
   155  	examples1v, err := examples1.At("2021-06-15~experimental")
   156  	c.Assert(err, qt.IsNil)
   157  
   158  	examples2, err := vervet.LoadResourceVersions(testdata.Path("conflict/_examples2"))
   159  	c.Assert(err, qt.IsNil)
   160  	examples2v, err := examples2.At("2021-06-15~experimental")
   161  	c.Assert(err, qt.IsNil)
   162  
   163  	err = collator.Collate(examples1v)
   164  	c.Assert(err, qt.IsNil)
   165  	err = collator.Collate(examples2v)
   166  	c.Assert(err, qt.IsNil)
   167  
   168  	result := collator.Result()
   169  
   170  	// First path chosen, route matching rules ignore path variable
   171  	c.Assert(result.Paths["/examples/hello-world/{id1}"], qt.Not(qt.IsNil))
   172  	c.Assert(result.Paths["/examples/hello-world/{id2}"], qt.IsNil)
   173  
   174  	// First chosen path has description expected
   175  	c.Assert(result.Paths["/examples/hello-world/{id1}"].Get.Description, qt.Contains, " - from example 1")
   176  }
   177  
   178  func TestCollatePathConflict(t *testing.T) {
   179  	c := qt.New(t)
   180  	collator := vervet.NewCollator(vervet.UseFirstRoute(false))
   181  	examples1, err := vervet.LoadResourceVersions(testdata.Path("conflict/_examples"))
   182  	c.Assert(err, qt.IsNil)
   183  	examples1v, err := examples1.At("2021-06-15~experimental")
   184  	c.Assert(err, qt.IsNil)
   185  
   186  	examples2, err := vervet.LoadResourceVersions(testdata.Path("conflict/_examples2"))
   187  	c.Assert(err, qt.IsNil)
   188  	examples2v, err := examples2.At("2021-06-15~experimental")
   189  	c.Assert(err, qt.IsNil)
   190  
   191  	err = collator.Collate(examples1v)
   192  	c.Assert(err, qt.IsNil)
   193  	err = collator.Collate(examples2v)
   194  	c.Assert(err, qt.ErrorMatches, `.*conflict in #/paths /examples/hello-world/{id2}: declared in both.*`)
   195  	c.Assert(err, qt.ErrorMatches, `.*conflict in #/paths /examples/hello-world: declared in both.*`)
   196  }
   197  
   198  func TestCollateMergingResources(t *testing.T) {
   199  	c := qt.New(t)
   200  	collator := vervet.NewCollator(vervet.UseFirstRoute(true))
   201  
   202  	newService, err := vervet.LoadResourceVersions(testdata.Path("competing-specs/special_projects"))
   203  	c.Assert(err, qt.IsNil)
   204  	specV1, err := newService.At("2023-03-13~experimental")
   205  	c.Assert(err, qt.IsNil)
   206  
   207  	originalService, err := vervet.LoadResourceVersions(testdata.Path("competing-specs/projects"))
   208  	c.Assert(err, qt.IsNil)
   209  	specV2, err := originalService.At("2021-08-20~experimental")
   210  	c.Assert(err, qt.IsNil)
   211  
   212  	err = collator.Collate(specV2)
   213  	c.Assert(err, qt.IsNil)
   214  	err = collator.Collate(specV1)
   215  	c.Assert(err, qt.IsNil)
   216  
   217  	result := collator.Result()
   218  	c.Assert(result.Paths["/orgs/{org_id}/projects/{project_id}"].Delete.Responses["204"], qt.IsNotNil)
   219  }