diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2677a1649..d90615b93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -169,9 +169,18 @@ jobs: keycloak.cache-from=type=gha,scope=keycloak-${{ github.ref }} keycloak.cache-from=type=gha,scope=keycloak-refs/heads/main keycloak.cache-to=type=gha,scope=keycloak-${{ github.ref }}-e2e,mode=max + - + name: Generate Mock Server Certificates + run: | + openssl req -x509 -newkey rsa:2048 -keyout e2e/mock-server/key.pem -out e2e/mock-server/cert.pem \ + -days 365 -nodes -subj '/CN=mock-server' \ + -addext 'subjectAltName=DNS:openlibrary.org,DNS:covers.openlibrary.org,DNS:gutendex.com' - name: Start Services run: docker compose up --wait --no-build + - + name: Trust Mock Server Certificate + run: docker compose exec -T php update-ca-certificates - name: Update API Platform if: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.minimum-stability != 'stable') }} diff --git a/api/migrations/Version20260318100000.php b/api/migrations/Version20260318100000.php new file mode 100644 index 000000000..a6db9299c --- /dev/null +++ b/api/migrations/Version20260318100000.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE bookmark DROP CONSTRAINT FK_DA62921D16A2B381'); + $this->addSql('ALTER TABLE bookmark ADD CONSTRAINT FK_DA62921D16A2B381 FOREIGN KEY (book_id) REFERENCES book (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + #[\Override] + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE bookmark DROP CONSTRAINT FK_DA62921D16A2B381'); + $this->addSql('ALTER TABLE bookmark ADD CONSTRAINT FK_DA62921D16A2B381 FOREIGN KEY (book_id) REFERENCES book (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } +} diff --git a/compose.e2e.yaml b/compose.e2e.yaml index 6264b9a74..78a6d9465 100644 --- a/compose.e2e.yaml +++ b/compose.e2e.yaml @@ -10,6 +10,13 @@ services: aliases: - openlibrary.org - covers.openlibrary.org + - gutendex.com + + php: + volumes: + - ./e2e/mock-server/cert.pem:/usr/local/share/ca-certificates/mock-server.crt:ro + depends_on: + - mock-openlibrary pwa: environment: diff --git a/e2e/mock-server/server.js b/e2e/mock-server/server.js index a6b83b6bf..5015a4c37 100644 --- a/e2e/mock-server/server.js +++ b/e2e/mock-server/server.js @@ -12,8 +12,8 @@ if (!fs.existsSync(KEY_PATH) || !fs.existsSync(CERT_PATH)) { console.log("Generating self-signed certificates..."); execSync( `openssl req -x509 -newkey rsa:2048 -keyout ${KEY_PATH} -out ${CERT_PATH} ` + - `-days 365 -nodes -subj '/CN=openlibrary.org' ` + - `-addext 'subjectAltName=DNS:openlibrary.org,DNS:covers.openlibrary.org'` + `-days 365 -nodes -subj '/CN=mock-server' ` + + `-addext 'subjectAltName=DNS:openlibrary.org,DNS:covers.openlibrary.org,DNS:gutendex.com'` ); } @@ -26,12 +26,18 @@ const server = https.createServer( // Try direct file path first (e.g. /books/OL2055137M.json) let filePath = path.join(MOCKS_DIR, host, url.pathname); - // Handle search.json?q=Title Author&limit=10 -> search/Title-Author.json + // Handle openlibrary search: /search.json?q=Title Author&limit=10 -> search/Title-Author.json if (url.pathname === "/search.json" && url.searchParams.has("q")) { const query = url.searchParams.get("q").replace(/\s+/g, "-"); filePath = path.join(MOCKS_DIR, host, "search", `${query}.json`); } + // Handle gutendex search: /books?search=... -> always return search/Asimov.json + // (MUI Autocomplete triggers additional searches with the full selected label) + if (host === "gutendex.com" && url.pathname === "/books" && url.searchParams.has("search")) { + filePath = path.join(MOCKS_DIR, host, "search", "Asimov.json"); + } + if (fs.existsSync(filePath)) { const ext = path.extname(filePath); res.writeHead(200, { diff --git a/e2e/tests/admin/pages/AbstractPage.ts b/e2e/tests/admin/pages/AbstractPage.ts index 8185c3e0d..2e6337341 100644 --- a/e2e/tests/admin/pages/AbstractPage.ts +++ b/e2e/tests/admin/pages/AbstractPage.ts @@ -34,5 +34,18 @@ export abstract class AbstractPage { await this.page.route(/^https:\/\/covers\.openlibrary.org\/b\/id\/(.+)\.jpg$/, (route) => route.fulfill({ path: "tests/mocks/covers.openlibrary.org/b/id/4066031-M.jpg", })); + // Gutendex mocks — always return the same search results (MUI Autocomplete + // triggers additional searches with the full selected label as query) + await this.page.route(/^https:\/\/gutendex\.com\/books\?search=/, (route) => { + return route.fulfill({ + path: "tests/mocks/gutendex.com/search/Asimov.json", + }); + }); + await this.page.route(/^https:\/\/gutendex\.com\/books\/(\d+)\.json$/, (route) => { + const match = route.request().url().match(/\/books\/(\d+)\.json/); + return route.fulfill({ + path: `tests/mocks/gutendex.com/books/${match?.[1]}.json`, + }); + }); } } diff --git a/e2e/tests/mocks/gutendex.com/books/31547.json b/e2e/tests/mocks/gutendex.com/books/31547.json new file mode 100644 index 000000000..e4d8c0313 --- /dev/null +++ b/e2e/tests/mocks/gutendex.com/books/31547.json @@ -0,0 +1,18 @@ +{ + "id": 31547, + "title": "Let's Get Together", + "authors": [ + { + "name": "Asimov, Isaac", + "birth_year": 1920, + "death_year": 1992 + } + ], + "subjects": ["Science fiction"], + "bookshelves": [], + "languages": ["en"], + "copyright": false, + "media_type": "Text", + "formats": {}, + "download_count": 1000 +} diff --git a/e2e/tests/mocks/gutendex.com/books/41547.json b/e2e/tests/mocks/gutendex.com/books/41547.json new file mode 100644 index 000000000..33c597d68 --- /dev/null +++ b/e2e/tests/mocks/gutendex.com/books/41547.json @@ -0,0 +1,18 @@ +{ + "id": 41547, + "title": "The Genetic Effects of Radiation", + "authors": [ + { + "name": "Asimov, Isaac", + "birth_year": 1920, + "death_year": 1992 + } + ], + "subjects": ["Science"], + "bookshelves": [], + "languages": ["en"], + "copyright": false, + "media_type": "Text", + "formats": {}, + "download_count": 500 +} diff --git a/e2e/tests/mocks/gutendex.com/search/Asimov.json b/e2e/tests/mocks/gutendex.com/search/Asimov.json new file mode 100644 index 000000000..89146319c --- /dev/null +++ b/e2e/tests/mocks/gutendex.com/search/Asimov.json @@ -0,0 +1,43 @@ +{ + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "id": 31547, + "title": "Let's Get Together", + "authors": [ + { + "name": "Asimov, Isaac", + "birth_year": 1920, + "death_year": 1992 + } + ], + "subjects": ["Science fiction"], + "bookshelves": [], + "languages": ["en"], + "copyright": false, + "media_type": "Text", + "formats": {}, + "download_count": 1000 + }, + { + "id": 41547, + "title": "The Genetic Effects of Radiation", + "authors": [ + { + "name": "Asimov, Isaac", + "birth_year": 1920, + "death_year": 1992 + } + ], + "subjects": ["Science"], + "bookshelves": [], + "languages": ["en"], + "copyright": false, + "media_type": "Text", + "formats": {}, + "download_count": 500 + } + ] +}