Skip to content

Commit d70d916

Browse files
authored
Fix broken reference in OpenAPI spec, improve validation tests (#4201)
1 parent db9ceb6 commit d70d916

File tree

8 files changed

+72
-56
lines changed

8 files changed

+72
-56
lines changed

.circleci/config.yml

-5
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,6 @@ workflows:
248248
parameters:
249249
dkan_recommended_branch: [ '10.1.x-dev']
250250
php_version: [ '8.2' ]
251-
- phpunit:
252-
matrix:
253-
parameters:
254-
dkan_recommended_branch: [ '10.0.x-dev']
255-
php_version: [ '8.1' ]
256251
upgrade_and_test:
257252
jobs:
258253
- cypress:

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"require-dev": {
4141
"drush/drush": "^12@stable",
4242
"getdkan/mock-chain": "^1.3.7",
43+
"osteel/openapi-httpfoundation-testing": "<1.0",
4344
"phpunit/phpunit": "^8.5.14 || ^9",
4445
"weitzman/drupal-test-traits": "^2.0.1"
4546
},

modules/common/tests/src/Functional/Api1TestBase.php

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use GuzzleHttp\RequestOptions;
99
use Opis\JsonSchema\Schema;
1010
use Opis\JsonSchema\Validator;
11+
use Osteel\OpenApi\Testing\ValidatorBuilder;
1112

1213
abstract class Api1TestBase extends BrowserTestBase {
1314
use UserCreationTrait;
@@ -23,6 +24,13 @@ abstract class Api1TestBase extends BrowserTestBase {
2324
protected $auth;
2425
protected $endpoint;
2526

27+
/**
28+
* OpenApi Validator.
29+
*
30+
* @var \Osteel\OpenApi\Testing\ValidatorInterface
31+
*/
32+
protected $validator;
33+
2634
protected $defaultTheme = 'stark';
2735
protected $strictConfigSchema = FALSE;
2836

@@ -49,6 +57,7 @@ public function setUp(): void {
4957

5058
// Load the API spec for use by tests.
5159
$response = $this->httpClient->request('GET', 'api/1');
60+
$this->validator = ValidatorBuilder::fromJsonString($response->getBody())->getValidator();
5261
$this->spec = json_decode($response->getBody());
5362
}
5463

modules/metastore/docs/openapi_spec.json

+39-12
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,35 @@
2525
}
2626
},
2727
"responses": {
28+
"200MetadataUpdated": {
29+
"description": "Metadata update successful.",
30+
"content": {
31+
"application/json": {
32+
"schema": {
33+
"$ref": "#/components/schemas/metastoreWriteResponse"
34+
}
35+
}
36+
}
37+
},
2838
"201MetadataCreated": {
2939
"description": "Metadata creation successful.",
3040
"content": {
3141
"application/json": {
3242
"schema": {
33-
"type": "object",
34-
"properties": {
35-
"endpoint": {
36-
"type": "string",
37-
"description": "Path to the metadata from the API."
38-
},
39-
"identifier": {
40-
"type": "string",
41-
"description": "Identifier for metadata just created or modified."
42-
}
43-
}
43+
"$ref": "#/components/schemas/metastoreWriteResponse"
44+
}
45+
}
46+
}
47+
},
48+
"409MetadataAlreadyExists": {
49+
"description": "Conflict; tried to create a record using an existing ID, or metadata contains identifier that doesn't match the request path.",
50+
"content": {
51+
"application/json": {
52+
"schema": { "$ref": "#/components/schemas/errorResponse" },
53+
"example": {
54+
"message": "dataset/00000000-0000-0000-0000-000000000000 already exists.",
55+
"status": 409,
56+
"timestamp": "2021-06-14T13:46:06+00:00"
4457
}
4558
}
4659
}
@@ -61,6 +74,20 @@
6174

6275
},
6376
"schemas": {
77+
"metastoreWriteResponse": {
78+
"type": "object",
79+
"properties": {
80+
"endpoint": {
81+
"type": "string",
82+
"description": "Path to the metadata from the API."
83+
},
84+
"identifier": {
85+
"type": "string",
86+
"description": "Identifier for metadata just created or modified."
87+
}
88+
},
89+
"additionalProperties": false
90+
},
6491
"metastoreRevision": {
6592
"type": "object",
6693
"properties": {
@@ -181,7 +208,7 @@
181208
],
182209
"responses": {
183210
"200": {
184-
"description": "Ok",
211+
"description": "Full list of all items for the given schema",
185212
"content": {
186213
"application/json": {
187214
"schema": {

modules/metastore/src/Plugin/DkanApiDocs/MetastoreApiDocs.php

+10-9
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function __construct(
4949
$pluginDefinition,
5050
ModuleHandlerInterface $moduleHandler,
5151
TranslationInterface $stringTranslation,
52-
MetastoreService $metastore
52+
MetastoreService $metastore,
5353
) {
5454
parent::__construct($configuration, $pluginId, $pluginDefinition, $moduleHandler, $stringTranslation);
5555
$this->metastore = $metastore;
@@ -73,7 +73,7 @@ public static function create(
7373
ContainerInterface $container,
7474
array $configuration,
7575
$pluginId,
76-
$pluginDefinition
76+
$pluginDefinition,
7777
) {
7878
return new static(
7979
$configuration,
@@ -296,6 +296,7 @@ private function schemaItemPost($schemaId, array $schema) {
296296
],
297297
],
298298
'400' => ['$ref' => '#/components/responses/400BadJson'],
299+
'409' => ['$ref' => '#/components/responses/409MetadataAlreadyExists'],
299300
],
300301
];
301302
}
@@ -347,7 +348,7 @@ private function schemaItemPut($schemaId) {
347348
return [
348349
"operationId" => "$schemaId-put",
349350
"summary" => $this->t("Replace a :schemaId", $tSchema),
350-
"description" => $this->t("Object will be completely replaced; optional properties not included in the request will be deleted.\n\nAutomatic example not yet available; try retrieving a :schemaId via GET, changing values, and pasting to test.", $tSchema),
351+
"description" => $this->t("Object will be completely replaced; optional properties not included in the request will be deleted.\n\nAutomatic example not yet available; try retrieving a :schemaId via GET, changing values, and pasting to test. If no item exists with the provided identifier, it will be created.", $tSchema),
351352
"tags" => [$this->t("Metastore: :schemaId", $tSchema)],
352353
"security" => [
353354
['basic_auth' => []],
@@ -362,9 +363,10 @@ private function schemaItemPut($schemaId) {
362363
],
363364
],
364365
"responses" => [
365-
"200" => [
366-
"description" => "Ok.",
367-
],
366+
"200" => ['$ref' => '#/components/responses/200MetadataUpdated'],
367+
"201" => ['$ref' => '#/components/responses/201MetadataCreated'],
368+
'400' => ['$ref' => '#/components/responses/400BadJson'],
369+
'409' => ['$ref' => '#/components/responses/409MetadataAlreadyExists'],
368370
"412" => ['$ref' => '#/components/responses/412MetadataObjectNotFound'],
369371
],
370372
];
@@ -401,9 +403,8 @@ private function schemaItemPatch($schemaId, array $schema) {
401403
],
402404
],
403405
"responses" => [
404-
"200" => [
405-
"description" => "Ok.",
406-
],
406+
"200" => ['$ref' => '#/components/responses/200MetadataUpdated'],
407+
'400' => ['$ref' => '#/components/responses/400BadJson'],
407408
"412" => ['$ref' => '#/components/responses/412MetadataObjectNotFound'],
408409
],
409410
];

modules/metastore/tests/src/Functional/Api1/DatasetItemTest.php

+11-22
Original file line numberDiff line numberDiff line change
@@ -19,40 +19,35 @@ public function testGet() {
1919

2020
$this->post($this->getSampleDataset(1));
2121

22-
$responseSchema = $this->spec->paths->{'/api/1/metastore/schemas/{schema_id}/items'}
23-
->get->responses->{"200"}->content->{"application/json"}->schema;
2422
$response = $this->httpClient->request('GET', $this->endpoint);
2523
$responseBody = json_decode($response->getBody());
2624
$this->assertEquals(2, count($responseBody));
2725
$this->assertTrue(is_object($responseBody[1]));
28-
$this->assertJsonIsValid($responseSchema, $responseBody);
26+
// Have to use this path because the endpoint as added is not in the spec.
27+
// @todo Simplify dataset vs {schema_id} items in the spec.
28+
$this->validator->validate($response, "api/1/metastore/schemas/{schema_id}/items", 'get');
2929

3030
$datasetId = 'abc-123';
3131
$response = $this->httpClient->get("$this->endpoint/$datasetId", [
3232
RequestOptions::HTTP_ERRORS => FALSE,
3333
]);
3434
$this->assertEquals(404, $response->getStatusCode());
3535

36-
$responseBody = json_decode($response->getBody());
37-
$responseSchema = $this->spec->components->responses->{"404IdNotFound"};
38-
$this->assertJsonIsValid($responseSchema, $responseBody);
36+
$this->validator->validate($response, "$this->endpoint/$datasetId", 'get');
3937
}
4038

4139
public function testPost() {
4240
$dataset = $this->getSampleDataset();
4341
$response = $this->post($dataset);
4442
$this->assertEquals(201, $response->getStatusCode());
4543

46-
$responseBody = json_decode($response->getBody());
47-
$responseSchema = $this->spec->components->responses->{"201MetadataCreated"}->content->{"application/json"}->schema;
48-
49-
$this->assertJsonIsValid($responseSchema, $responseBody);
44+
$this->validator->validate($response, $this->endpoint, 'post');
5045
$this->assertDatasetGet($dataset);
5146

5247
// Now try a duplicate.
5348
$response = $this->post($dataset, FALSE);
5449
$this->assertEquals(409, $response->getStatusCode());
55-
// @todo Fuly validate response once documented.
50+
$this->validator->validate($response, $this->endpoint, 'post');
5651
}
5752

5853
public function testPatch() {
@@ -68,9 +63,7 @@ public function testPatch() {
6863

6964
$this->assertEquals(200, $response->getStatusCode());
7065

71-
$responseBody = json_decode($response->getBody());
72-
$responseSchema = $this->spec->components->responses->{"201MetadataCreated"}->content->{"application/json"}->schema;
73-
$this->assertJsonIsValid($responseSchema, $responseBody);
66+
$this->validator->validate($response, "$this->endpoint/$datasetId", 'patch');
7467

7568
$dataset->title = $newTitle->title;
7669
$this->assertDatasetGet($dataset);
@@ -86,10 +79,8 @@ public function testPatch() {
8679
]);
8780

8881
$this->assertEquals(412, $response->getStatusCode());
82+
$this->validator->validate($response, "$this->endpoint/$datasetId", 'patch');
8983

90-
$responseBody = json_decode($response->getBody());
91-
$responseSchema = $this->spec->components->responses->{"412MetadataObjectNotFound"};
92-
$this->assertJsonIsValid($responseSchema, $responseBody);
9384
}
9485

9586
public function testPut() {
@@ -105,9 +96,7 @@ public function testPut() {
10596
RequestOptions::AUTH => $this->auth,
10697
]);
10798
$this->assertEquals(200, $response->getStatusCode());
108-
$responseBody = json_decode($response->getBody());
109-
$responseSchema = $this->spec->components->responses->{"201MetadataCreated"}->content->{"application/json"}->schema;
110-
$this->assertJsonIsValid($responseSchema, $responseBody);
99+
$this->validator->validate($response, "$this->endpoint/$datasetId", 'put');
111100
$this->assertDatasetGet($newDataset);
112101

113102
// Now try with mismatched identifiers.
@@ -118,15 +107,15 @@ public function testPut() {
118107
RequestOptions::HTTP_ERRORS => FALSE,
119108
]);
120109
$this->assertEquals(409, $response->getStatusCode());
110+
$this->validator->validate($response, "$this->endpoint/$datasetId", 'put');
121111
}
122112

123113
private function assertDatasetGet($dataset) {
124114
$id = $dataset->identifier;
125-
$responseSchema = $this->spec->components->schemas->dataset;
126115
$response = $this->httpClient->get("$this->endpoint/$id");
127116
$responseBody = json_decode($response->getBody());
128117
$this->assertEquals(200, $response->getStatusCode());
129-
$this->assertJsonIsValid($responseSchema, $responseBody);
118+
$this->validator->validate($response, "$this->endpoint/$id", 'get');
130119
$this->assertEquals($dataset, $responseBody);
131120
}
132121

modules/metastore/tests/src/Functional/Api1/DatasetRevisionTest.php

+2-6
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ public function testList() {
2020
]);
2121

2222
// Test individual item endpoint.
23-
$responseSchema = $this->spec->components->responses->{"201MetadataCreated"}->content->{"application/json"}->schema;
24-
25-
$responseBody = json_decode($response->getBody());
26-
$this->assertJsonIsValid($responseSchema, $responseBody);
23+
$this->validator->validate($response, "api/1/metastore/schemas/dataset/items", 'post');
2724

2825
$response = $this->httpClient->get($this->endpoint, [
2926
RequestOptions::AUTH => $this->auth,
@@ -115,7 +112,6 @@ public function testPost() {
115112
'archived' => FALSE,
116113
'hidden' => TRUE,
117114
];
118-
$responseSchema = $this->spec->components->responses->{"201MetadataCreated"}->content->{"application/json"}->schema;
119115

120116
$count = 1;
121117
foreach ($states as $state => $public) {
@@ -126,7 +122,7 @@ public function testPost() {
126122

127123
// Validate response object.
128124
$responseBody = json_decode($response->getBody());
129-
$this->assertJsonIsValid($responseSchema, $responseBody);
125+
$this->validator->validate($response, $this->endpoint, 'post');
130126

131127
// Validate URL and contents of response object.
132128
$response = $this->httpClient->get($responseBody->endpoint, [

modules/metastore/tests/src/Functional/Api1/DistributionHandlingTest.php

-2
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,7 @@ private function postDataDictionary() {
9595
$this->assertEquals(201, $response->getStatusCode());
9696

9797
$responseBody = json_decode($response->getBody());
98-
$responseSchema = $this->spec->components->responses->{"201MetadataCreated"}->content->{"application/json"}->schema;
9998

100-
$this->assertJsonIsValid($responseSchema, $responseBody);
10199
// Unless JSON changes, we should always get same id back.
102100
$this->assertEquals("47f1d697-f469-5b41-a613-80cdfac7a326", $responseBody->identifier);
103101

0 commit comments

Comments
 (0)