Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Slim4] Add array support to Data Mocker #4801

Merged
merged 4 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,19 @@ final class OpenApiDataMocker implements IMocker
return $this->mockString($dataFormat, $minLength, $maxLength);
case IMocker::DATA_TYPE_BOOLEAN:
return $this->mockBoolean();
case IMocker::DATA_TYPE_ARRAY:
$items = $options['items'] ?? null;
$minItems = $options['minItems'] ?? 0;
$maxItems = $options['maxItems'] ?? null;
$uniqueItems = $options['uniqueItems'] ?? false;
return $this->mockArray($items, $minItems, $maxItems, $uniqueItems);
default:
throw new InvalidArgumentException('"dataType" must be one of ' . implode(', ', [
IMocker::DATA_TYPE_INTEGER,
IMocker::DATA_TYPE_NUMBER,
IMocker::DATA_TYPE_STRING,
IMocker::DATA_TYPE_BOOLEAN,
IMocker::DATA_TYPE_ARRAY,
]));
}
}
Expand Down Expand Up @@ -209,6 +216,101 @@ final class OpenApiDataMocker implements IMocker
return (bool) mt_rand(0, 1);
}

/**
* Shortcut to mock array type
* Equivalent to mockData(DATA_TYPE_ARRAY);
*
* @param array $items Array of described items
* @param int|null $minItems (optional) An array instance is valid against "minItems" if its size is greater than, or equal to, the value of this keyword.
* @param int|null $maxItems (optional) An array instance is valid against "maxItems" if its size is less than, or equal to, the value of this keyword
* @param bool|null $uniqueItems (optional) If it has boolean value true, the instance validates successfully if all of its elements are unique
*
* @throws \InvalidArgumentException when invalid arguments passed
*
* @return array
*/
public function mockArray(
$items,
$minItems = 0,
$maxItems = null,
$uniqueItems = false
) {
$arr = [];
$minSize = 0;
$maxSize = \PHP_INT_MAX;

if (is_array($items) === false || array_key_exists('type', $items) === false) {
throw new InvalidArgumentException('"items" must be assoc array with "type" key');
}

if ($minItems !== null) {
if (is_integer($minItems) === false || $minItems < 0) {
throw new InvalidArgumentException('"mitItems" must be an integer. This integer must be greater than, or equal to, 0');
}
$minSize = $minItems;
}

if ($maxItems !== null) {
if (is_integer($maxItems) === false || $maxItems < 0) {
throw new InvalidArgumentException('"maxItems" must be an integer. This integer must be greater than, or equal to, 0.');
}
if ($maxItems < $minItems) {
throw new InvalidArgumentException('"maxItems" value cannot be less than "minItems"');
}
$maxSize = $maxItems;
}

$dataType = $items['type'];
$dataFormat = $items['format'] ?? null;
$options = $this->extractSchemaProperties($items);

// always genarate smallest possible array to avoid huge JSON responses
$arrSize = ($maxSize < 1) ? $maxSize : max($minSize, 1);
while (count($arr) < $arrSize) {
$arr[] = $this->mock($dataType, $dataFormat, $options);
}
return $arr;
}

/**
* @internal Extract OAS properties from array or object.
*
* @param array $arr Processed array
*
* @return array
*/
private function extractSchemaProperties($arr)
{
$props = [];
foreach (
[
'minimum',
'maximum',
'exclusiveMinimum',
'exclusiveMaximum',
'minLength',
'maxLength',
'pattern',
'enum',
'items',
'minItems',
'maxItems',
'uniqueItems',
'properties',
'minProperties',
'maxProperties',
'additionalProperties',
'required',
'example',
] as $propName
) {
if (array_key_exists($propName, $arr)) {
$props[$propName] = $arr[$propName];
}
}
return $props;
}

/**
* @internal
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ interface {{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}}
/** @var string DATA_TYPE_FILE */
public const DATA_TYPE_FILE = 'file';

/** @var string DATA_TYPE_ARRAY */
public const DATA_TYPE_ARRAY = 'array';

/** @var string DATA_FORMAT_INT32 Signed 32 bits */
public const DATA_FORMAT_INT32 = 'int32';

Expand Down Expand Up @@ -186,5 +189,25 @@ interface {{interfaceNamePrefix}}OpenApiDataMocker{{interfaceNameSuffix}}
* @return bool
*/
public function mockBoolean();

/**
* Shortcut to mock array type
* Equivalent to mockData(DATA_TYPE_ARRAY);
*
* @param array $items Array of described items
* @param int|null $minItems (optional) An array instance is valid against "minItems" if its size is greater than, or equal to, the value of this keyword.
* @param int|null $maxItems (optional) An array instance is valid against "maxItems" if its size is less than, or equal to, the value of this keyword
* @param bool|null $uniqueItems (optional) If it has boolean value true, the instance validates successfully if all of its elements are unique
*
* @throws \InvalidArgumentException when invalid arguments passed
*
* @return array
*/
public function mockArray(
$items,
$minItems = 0,
$maxItems = null,
$uniqueItems = false
);
}
{{/apiInfo}}
Original file line number Diff line number Diff line change
Expand Up @@ -416,5 +416,172 @@ class OpenApiDataMockerTest extends TestCase
$this->assertContains($str, $enum);
}
}

/**
* @dataProvider provideMockArrayCorrectArguments
* @covers ::mockArray
*/
public function testMockArrayFlattenWithCorrectArguments(
$items,
$minItems,
$maxItems,
$uniqueItems,
$expectedItemsType = null,
$expectedArraySize = null
) {
$mocker = new OpenApiDataMocker();
$arr = $mocker->mockArray($items, $minItems, $maxItems, $uniqueItems);

$this->assertIsArray($arr);
if ($expectedArraySize !== null) {
$this->assertCount($expectedArraySize, $arr);
}
if ($expectedItemsType && $expectedArraySize > 0) {
$this->assertContainsOnly($expectedItemsType, $arr, true);
}

$dataFormat = $items['dataFormat'] ?? null;

// items field numeric properties
$minimum = $items['minimum'] ?? null;
$maximum = $items['maximum'] ?? null;
$exclusiveMinimum = $items['exclusiveMinimum'] ?? null;
$exclusiveMaximum = $items['exclusiveMaximum'] ?? null;

// items field string properties
$minLength = $items['minLength'] ?? null;
$maxLength = $items['maxLength'] ?? null;
$enum = $items['enum'] ?? null;
$pattern = $items['pattern'] ?? null;

// items field array properties
$subItems = $items['items'] ?? null;
$subMinItems = $items['minItems'] ?? null;
$subMaxItems = $items['maxItems'] ?? null;
$subUniqueItems = $items['uniqueItems'] ?? null;

foreach ($arr as $item) {
switch ($items['type']) {
case IMocker::DATA_TYPE_INTEGER:
$this->internalAssertNumber($item, $minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum);
break;
case IMocker::DATA_TYPE_NUMBER:
$this->internalAssertNumber($item, $minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum);
break;
case IMocker::DATA_TYPE_STRING:
$this->internalAssertString($item, $minLength, $maxLength);
break;
case IMocker::DATA_TYPE_BOOLEAN:
$this->assertInternalType(IsType::TYPE_BOOL, $item);
break;
case IMocker::DATA_TYPE_ARRAY:
$this->testMockArrayFlattenWithCorrectArguments($subItems, $subMinItems, $subMaxItems, $subUniqueItems);
break;
}
}
}

public function provideMockArrayCorrectArguments()
{
$intItems = ['type' => IMocker::DATA_TYPE_INTEGER, 'minimum' => 5, 'maximum' => 10];
$floatItems = ['type' => IMocker::DATA_TYPE_NUMBER, 'minimum' => -32.4, 'maximum' => 88.6, 'exclusiveMinimum' => true, 'exclusiveMaximum' => true];
$strItems = ['type' => IMocker::DATA_TYPE_STRING, 'minLength' => 20, 'maxLength' => 50];
$boolItems = ['type' => IMocker::DATA_TYPE_BOOLEAN];
$arrayItems = ['type' => IMocker::DATA_TYPE_ARRAY, 'items' => ['type' => IMocker::DATA_TYPE_STRING, 'minItems' => 3, 'maxItems' => 10]];
$expectedInt = IsType::TYPE_INT;
$expectedFloat = IsType::TYPE_FLOAT;
$expectedStr = IsType::TYPE_STRING;
$expectedBool = IsType::TYPE_BOOL;
$expectedArray = IsType::TYPE_ARRAY;

return [
'empty array' => [
$strItems, null, 0, false, null, 0,
],
'empty array, limit zero' => [
$strItems, 0, 0, false, null, 0,
],
'array of one string as default size' => [
$strItems, null, null, false, $expectedStr, 1,
],
'array of one string, limit one' => [
$strItems, 1, 1, false, $expectedStr, 1,
],
'array of two strings' => [
$strItems, 2, null, false, $expectedStr, 2,
],
'array of five strings, limit ten' => [
$strItems, 5, 10, false, $expectedStr, 5,
],
'array of five strings, limit five' => [
$strItems, 5, 5, false, $expectedStr, 5,
],
'array of one string, limit five' => [
$strItems, null, 5, false, $expectedStr, 1,
],
'array of one integer' => [
$intItems, null, null, false, $expectedInt, 1,
],
'array of one float' => [
$floatItems, null, null, false, $expectedFloat, 1,
],
'array of one boolean' => [
$boolItems, null, null, false, $expectedBool, 1,
],
'array of one array of strings' => [
$arrayItems, null, null, false, $expectedArray, 1,
],
];
}

/**
* @dataProvider provideMockArrayInvalidArguments
* @expectedException \InvalidArgumentException
* @covers ::mockArray
*/
public function testMockArrayWithInvalidArguments(
$items,
$minItems,
$maxItems,
$uniqueItems
) {
$mocker = new OpenApiDataMocker();
$arr = $mocker->mockArray($items, $minItems, $maxItems, $uniqueItems);
}

public function provideMockArrayInvalidArguments()
{
$intItems = ['type' => IMocker::DATA_TYPE_INTEGER];

return [
'items is nor array' => [
'foobar', null, null, false,
],
'items doesnt have "type" key' => [
['foobar' => 'foobaz'], null, null, false,
],
'minItems is not integer' => [
$intItems, 3.12, null, false,
],
'minItems is negative' => [
$intItems, -10, null, false,
],
'minItems is not number' => [
$intItems, '1', null, false,
],
'maxItems is not integer' => [
$intItems, null, 3.12, false,
],
'maxItems is negative' => [
$intItems, null, -10, false,
],
'maxItems is not number' => [
$intItems, null, 'foobaz', false,
],
'maxItems less than minItems' => [
$intItems, 5, 2, false,
],
];
}
}
{{/apiInfo}}
Loading