Skip to content

Commit

Permalink
chore: enable all tests to expose failing ones (+ improve admin and c…
Browse files Browse the repository at this point in the history
…lient)
  • Loading branch information
vincentchalamon committed Aug 4, 2023
1 parent d67f6b8 commit 75daae6
Show file tree
Hide file tree
Showing 25 changed files with 495 additions and 476 deletions.
2 changes: 1 addition & 1 deletion api/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"ext-ctype": "*",
"ext-iconv": "*",
"ext-xml": "*",
"api-platform/core": "dev-demo",
"api-platform/core": "dev-fix/issues/5662",
"doctrine/doctrine-bundle": "^2.7",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.12",
Expand Down
535 changes: 271 additions & 264 deletions api/composer.lock

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions api/config/packages/api_platform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ api_platform:
stateless: true
cache_headers:
vary: ['Content-Type', 'Authorization', 'Origin']
extra_properties:
standard_put: true
oauth:
enabled: true
clientId: '%env(OIDC_SWAGGER_CLIENT_ID)%'
Expand Down
2 changes: 1 addition & 1 deletion api/src/DataFixtures/Story/DefaultBookStory.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function build(): void
BookFactory::createOne([
'condition' => BookCondition::NewCondition,
'book' => 'https://openlibrary.org/books/OL6095440M.json',
'title' => 'Fondation',
'title' => 'Foundation',
'author' => 'Isaac Asimov',
]);

Expand Down
6 changes: 4 additions & 2 deletions api/src/Entity/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App\Entity;

use ApiPlatform\Doctrine\Common\Filter\SearchFilterInterface;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiProperty;
Expand Down Expand Up @@ -91,18 +92,19 @@ class Book
)]
#[Assert\NotBlank(allowNull: false)]
#[Assert\Url(protocols: ['https'])]
#[Assert\Regex(pattern: '/^https:\/\/openlibrary.org\/books\/OL\d{8}M\.json$/')]
#[Assert\Regex(pattern: '/^https:\/\/openlibrary.org\/books\/OL\d+M\.json$/')]
#[Groups(groups: ['Book:read', 'Book:read:admin', 'Bookmark:read', 'Book:write'])]
#[ORM\Column(unique: true)]
public ?string $book = null;

/**
* @see https://schema.org/name
*/
#[ApiFilter(OrderFilter::class)]
#[ApiFilter(SearchFilter::class, strategy: 'i'.SearchFilterInterface::STRATEGY_PARTIAL)]
#[ApiProperty(
types: ['https://schema.org/name'],
example: 'Fondation'
example: 'Foundation'
)]
#[Groups(groups: ['Book:read', 'Book:read:admin', 'Bookmark:read', 'Review:read:admin'])]
#[ORM\Column(type: Types::TEXT)]
Expand Down
41 changes: 24 additions & 17 deletions api/tests/Api/Admin/BookTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,26 @@ public function getUrls(): iterable
];
}

public function testAsAdminUserICanGetACollectionOfBooksOrderedByTitle(): void
{
BookFactory::createOne(['title' => 'Foundation']);
BookFactory::createOne(['title' => 'Nemesis']);
BookFactory::createOne(['title' => 'I, Robot']);

$token = self::getContainer()->get(OidcTokenGenerator::class)->generate([
'email' => UserFactory::createOneAdmin()->email,
]);

$response = $this->client->request('GET', '/admin/books?order[title]=asc', ['auth_bearer' => $token]);

self::assertResponseIsSuccessful();
self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
self::assertEquals('Foundation', $response->toArray()['hydra:member'][0]['title']);
self::assertEquals('I, Robot', $response->toArray()['hydra:member'][1]['title']);
self::assertEquals('Nemesis', $response->toArray()['hydra:member'][2]['title']);
self::assertMatchesJsonSchema(file_get_contents(__DIR__.'/schemas/Book/collection.json'));
}

/**
* @dataProvider getAllUsers
*/
Expand Down Expand Up @@ -318,14 +338,11 @@ public function testAsNonAdminUserICannotUpdateBook(int $expectedCode, string $h
$options['auth_bearer'] = $token;
}

$this->client->request('PATCH', '/admin/books/'.$book->getId(), $options + [
$this->client->request('PUT', '/admin/books/'.$book->getId(), $options + [
'json' => [
'book' => 'https://openlibrary.org/books/OL28346544M.json',
'condition' => BookCondition::NewCondition->value,
],
'headers' => [
'Content-Type' => 'application/merge-patch+json',
],
]);

self::assertResponseStatusCodeSame($expectedCode);
Expand All @@ -346,14 +363,11 @@ public function testAsAdminUserICannotUpdateAnInvalidBook(): void
'email' => UserFactory::createOneAdmin()->email,
]);

$this->client->request('PATCH', '/admin/books/invalid', [
$this->client->request('PUT', '/admin/books/invalid', [
'auth_bearer' => $token,
'json' => [
'condition' => BookCondition::DamagedCondition->value,
],
'headers' => [
'Content-Type' => 'application/merge-patch+json',
],
]);

self::assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
Expand All @@ -364,19 +378,15 @@ public function testAsAdminUserICannotUpdateAnInvalidBook(): void
*/
public function testAsAdminUserICannotUpdateABookWithInvalidData(array $data, array $violations): void
{
$this->markTestIncomplete('Invalid identifier value or configuration.');
BookFactory::createOne();

$token = self::getContainer()->get(OidcTokenGenerator::class)->generate([
'email' => UserFactory::createOneAdmin()->email,
]);

$this->client->request('PATCH', '/admin/books/invalid', [
$this->client->request('PUT', '/admin/books/invalid', [
'auth_bearer' => $token,
'json' => $data,
'headers' => [
'Content-Type' => 'application/merge-patch+json',
],
]);

self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
Expand All @@ -402,14 +412,11 @@ public function testAsAdminUserICanUpdateABook(): void
'email' => UserFactory::createOneAdmin()->email,
]);

$this->client->request('PATCH', '/admin/books/'.$book->getId(), [
$this->client->request('PUT', '/admin/books/'.$book->getId(), [
'auth_bearer' => $token,
'json' => [
'condition' => BookCondition::DamagedCondition->value,
],
'headers' => [
'Content-Type' => 'application/merge-patch+json',
],
]);

self::assertResponseStatusCodeSame(Response::HTTP_OK);
Expand Down
10 changes: 2 additions & 8 deletions api/tests/Api/Admin/ReviewTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,12 @@ public function testAsAdminUserICannotUpdateAnInvalidReview(): void
'email' => UserFactory::createOneAdmin()->email,
]);

$this->client->request('PATCH', '/admin/reviews/invalid', [
$this->client->request('PUT', '/admin/reviews/invalid', [
'auth_bearer' => $token,
'json' => [
'body' => 'Very good book!',
'rating' => 5,
],
'headers' => [
'Content-Type' => 'application/merge-patch+json',
],
]);

self::assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
Expand All @@ -251,15 +248,12 @@ public function testAsAdminUserICanUpdateAReview(): void
'email' => UserFactory::createOneAdmin()->email,
]);

$this->client->request('PATCH', '/admin/reviews/'.$review->getId(), [
$this->client->request('PUT', '/admin/reviews/'.$review->getId(), [
'auth_bearer' => $token,
'json' => [
'body' => 'Very good book!',
'rating' => 5,
],
'headers' => [
'Content-Type' => 'application/merge-patch+json',
],
]);

self::assertResponseStatusCodeSame(Response::HTTP_OK);
Expand Down
16 changes: 16 additions & 0 deletions api/tests/Api/BookTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ public function getUrls(): iterable
];
}

public function testAsAdminUserICanGetACollectionOfBooksOrderedByTitle(): void
{
BookFactory::createOne(['title' => 'Foundation']);
BookFactory::createOne(['title' => 'Nemesis']);
BookFactory::createOne(['title' => 'I, Robot']);

$response = $this->client->request('GET', '/books?order[title]=asc');

self::assertResponseIsSuccessful();
self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
self::assertEquals('Foundation', $response->toArray()['hydra:member'][0]['title']);
self::assertEquals('I, Robot', $response->toArray()['hydra:member'][1]['title']);
self::assertEquals('Nemesis', $response->toArray()['hydra:member'][2]['title']);
self::assertMatchesJsonSchema(file_get_contents(__DIR__.'/schemas/Book/collection.json'));
}

public function testAsAnonymousICannotGetAnInvalidBook(): void
{
BookFactory::createOne();
Expand Down
1 change: 0 additions & 1 deletion api/tests/Api/BookmarkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ public function testAsAnonymousICannotCreateABookmark(): void

public function testAsAUserICannotCreateABookmarkWithInvalidData(): void
{
$this->markTestIncomplete('Identifier "id" could not be transformed.');
$token = self::getContainer()->get(OidcTokenGenerator::class)->generate([
'email' => UserFactory::createOne()->email,
]);
Expand Down
32 changes: 18 additions & 14 deletions api/tests/Api/ReviewTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,24 +190,25 @@ public function getInvalidData(): iterable
],
],
];
// yield [
// [
// 'book' => 'invalid book',
// 'body' => 'Very good book!',
// 'rating' => 5,
// ],
// [
// [
// 'propertyPath' => 'book',
// 'message' => 'This value is not a valid URL.',
// ],
// ]
// ];
yield [
[
'book' => 'invalid book',
'body' => 'Very good book!',
'rating' => 5,
],
[
[
'propertyPath' => 'book',
'message' => 'This value is not a valid URL.',
],
]
];
}

public function testAsAUserICanAddAReviewOnABook(): void
{
$book = BookFactory::createOne();
$book = BookFactory::createOne()->disableAutoRefresh();
ReviewFactory::createMany(5, ['book' => $book]);
$user = UserFactory::createOne();

$token = self::getContainer()->get(OidcTokenGenerator::class)->generate([
Expand All @@ -234,6 +235,9 @@ public function testAsAUserICanAddAReviewOnABook(): void
'rating' => 5,
]);
self::assertMatchesJsonSchema(file_get_contents(__DIR__.'/schemas/Review/item.json'));
// if I add a review on a book with reviews, it doesn't erase the existing reviews
$reviews = self::getContainer()->get(ReviewRepository::class)->findBy(['book' => $book]);
self::assertCount(6, $reviews);
}

public function testAsAnonymousICannotGetAnInvalidReview(): void
Expand Down
43 changes: 24 additions & 19 deletions pwa/components/admin/book/BookInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { forwardRef, SyntheticEvent, useRef, useState } from "react";
import { SyntheticEvent, useMemo, useRef, useState } from "react";
import Autocomplete from "@mui/material/Autocomplete";
import { debounce } from "@mui/material";
import { TextInput, type TextInputProps, useInput } from "react-admin";
Expand All @@ -9,14 +9,14 @@ import { Search } from "@/types/OpenLibrary/Search";
import { SearchDoc } from "@/types/OpenLibrary/SearchDoc";

interface Result {
title: string
author: string
value: string
title: string;
author: string;
value: string;
}

interface BookInputProps extends TextInputProps {
title?: string
author?: string
title?: string;
author?: string;
}

const fetchOpenLibrarySearch = async (query: string, signal?: AbortSignal | undefined): Promise<Array<Result>> => {
Expand Down Expand Up @@ -54,16 +54,14 @@ const fetchOpenLibrarySearch = async (query: string, signal?: AbortSignal | unde
};

export const BookInput = (props: BookInputProps) => {
const { field } = useInput(props);
const { field: { ref, ...field} } = useInput(props);
const title = useWatch({ name: "title" });
const author = useWatch({ name: "author" });
const controller = useRef<AbortController | undefined>();
const [searchQuery, setSearchQuery] = useState<string>("");
const initialData = !!title && !!author && !!field.value ? {
title: title,
author: author,
value: field.value,
} : undefined;
const [value, setValue] = useState<Result | null | undefined>(
!!title && !!author && !!field.value ? { title: title, author: author, value: field.value } : undefined
);
const { isLoading, data, isFetched } = useQuery<Result[]>(
["search", searchQuery],
async () => {
Expand All @@ -78,15 +76,22 @@ export const BookInput = (props: BookInputProps) => {
enabled: !!searchQuery,
}
);
const onInputChange = useMemo(() =>
debounce((event: SyntheticEvent, value: string) => setSearchQuery(value), 400),
[]
);
const onChange = (event: SyntheticEvent, value: Result | null | undefined) => {
field.onChange(value?.value);
setValue(value);
};

return <Autocomplete
defaultValue={initialData}
isOptionEqualToValue={(option, value) => option.value === value.value}
// @ts-ignore
options={!isFetched ? [initialData] : data}
onChange={(event: SyntheticEvent, value: Result | null) => field.value = value?.value ?? ""}
onInputChange={debounce((event: SyntheticEvent, value: string) => setSearchQuery(value), 400)}
getOptionLabel={(option: Result) => `${option.title} - ${option.author}`}
value={value}
options={!isFetched ? (!!value ? [value] : []) : (data ?? [])}
isOptionEqualToValue={(option, val) => option?.value === (val?.value || value?.value)}
onChange={onChange}
onInputChange={onInputChange}
getOptionLabel={(option: Result | undefined) => !!option ? `${option.title} - ${option.author}` : "No options"}
style={{ width: 500 }}
loading={isLoading}
renderInput={(params) => (
Expand Down
9 changes: 4 additions & 5 deletions pwa/components/admin/book/ConditionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { SelectInput, SelectInputProps } from "react-admin";

export const ConditionInput = (props: SelectInputProps) => (
<SelectInput {...props} choices={[
/*todo translate condition*/
{ id: "https://schema.org/NewCondition", name: "NewCondition" },
{ id: "https://schema.org/RefurbishedCondition", name: "RefurbishedCondition" },
{ id: "https://schema.org/DamagedCondition", name: "DamagedCondition" },
{ id: "https://schema.org/UsedCondition", name: "UsedCondition" },
{ id: "https://schema.org/NewCondition", name: "New" },
{ id: "https://schema.org/RefurbishedCondition", name: "Refurbished" },
{ id: "https://schema.org/DamagedCondition", name: "Damaged" },
{ id: "https://schema.org/UsedCondition", name: "Used" },
]}/>
);
3 changes: 1 addition & 2 deletions pwa/components/admin/book/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ import { ConditionInput } from "@/components/admin/book/ConditionInput";
const ConditionField = (props: UseRecordContextParams) => {
const record = useRecordContext(props);

// todo translate condition
return !!record && !!record.condition ? <span>{record.condition.replace("https://schema.org/", "")}</span> : null;
return !!record && !!record.condition ? <span>{record.condition.replace(/https:\/\/schema\.org\/(.+)Condition$/, "$1")}</span> : null;
};
ConditionField.defaultProps = { label: "Condition" };

Expand Down
Loading

0 comments on commit 75daae6

Please sign in to comment.