From 3f85fc4080399882630061d7ed7e62f5e44eed45 Mon Sep 17 00:00:00 2001 From: Aaron Ji Date: Wed, 9 Apr 2025 16:35:10 +0800 Subject: [PATCH] chore: add e2e tests --- package-lock.json | 212 ++++++++++++++++++++++++++++++++------ package.json | 5 +- test/e2e/search.test.ts | 182 ++++++++++++++++++++++++++++++++ test/jest.config.js | 30 ++++++ test/tsconfig.test.json | 18 ++++ test/utils/http-client.ts | 67 ++++++++++++ 6 files changed, 481 insertions(+), 33 deletions(-) create mode 100644 test/e2e/search.test.ts create mode 100644 test/jest.config.js create mode 100644 test/tsconfig.test.json create mode 100644 test/utils/http-client.ts diff --git a/package-lock.json b/package-lock.json index 35f1d1e..bc44dea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,7 @@ "@types/bcrypt": "^5.0.0", "@types/busboy": "^1.5.4", "@types/cors": "^2.8.17", + "@types/jest": "^29.5.14", "@types/koa": "^2.15.0", "@types/koa-compress": "^4.0.6", "@types/node": "^20.14.13", @@ -67,6 +68,7 @@ "firebase-functions-test": "^3.0.0", "pino-pretty": "^13.0.0", "replicate": "^0.16.1", + "ts-jest": "^29.3.1", "typescript": "^5.5.4" }, "engines": { @@ -1355,7 +1357,6 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, - "peer": true, "dependencies": { "jest-get-type": "^29.6.3" }, @@ -1467,7 +1468,6 @@ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, - "peer": true, "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -1554,7 +1554,6 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -2207,8 +2206,7 @@ "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@sinonjs/commons": { "version": "3.0.1", @@ -2433,15 +2431,13 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, - "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -2451,11 +2447,21 @@ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, - "peer": true, "dependencies": { "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2640,8 +2646,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/tough-cookie": { "version": "4.0.5", @@ -2679,7 +2684,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -2688,8 +2692,7 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/yauzl": { "version": "2.10.3", @@ -3716,6 +3719,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -4010,7 +4026,6 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], - "peer": true, "engines": { "node": ">=8" } @@ -4702,7 +4717,6 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -4822,6 +4836,22 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.735", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.735.tgz", @@ -5478,7 +5508,6 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, - "peer": true, "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -5762,6 +5791,39 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -7415,6 +7477,25 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -7595,7 +7676,6 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -7659,7 +7739,6 @@ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -7709,7 +7788,6 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -7725,7 +7803,6 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -7944,7 +8021,6 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -8142,7 +8218,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "peer": true, "bin": { "json5": "lib/cli.js" }, @@ -8623,6 +8698,13 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -8707,6 +8789,13 @@ "semver": "bin/semver.js" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/make-fetch-happen": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", @@ -10463,7 +10552,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -10478,7 +10566,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -10967,8 +11054,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/readable-stream": { "version": "3.6.2", @@ -11299,9 +11385,10 @@ "license": "BSD-3-Clause" }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -11735,7 +11822,6 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, - "peer": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -11748,7 +11834,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -12226,6 +12311,69 @@ "integrity": "sha512-3phiGcxPSSR47RBubQxPoZ+pqXsEsozLo4G4AlSrsMKTFg9TA3l+3he5BqpUi9wiuDbaHWXH/amlzQ49uEdXtg==", "dev": true }, + "node_modules/ts-jest": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.1.tgz", + "integrity": "sha512-FT2PIRtZABwl6+ZCry8IY7JZ3xMuppsEV9qFVHOVe8jDzggwUZ9TsM4chyJxL9yi6LvkqcZYU3LmapEE454zBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.1", + "type-fest": "^4.38.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.39.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", + "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", diff --git a/package.json b/package.json index ae35f72..6a9ca60 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "serve": "npm run build && npm run start", "debug": "npm run build && npm run dev", "start": "node ./build/stand-alone/crawl.js", - "dry-run": "NODE_ENV=dry-run node ./build/stand-alone/search.js" + "dry-run": "NODE_ENV=dry-run node ./build/stand-alone/search.js", + "test": "NODE_OPTIONS=--experimental-vm-modules jest --config ./test/jest.config.js" }, "engines": { "node": ">=18" @@ -63,6 +64,7 @@ "@types/bcrypt": "^5.0.0", "@types/busboy": "^1.5.4", "@types/cors": "^2.8.17", + "@types/jest": "^29.5.14", "@types/koa": "^2.15.0", "@types/koa-compress": "^4.0.6", "@types/node": "^20.14.13", @@ -76,6 +78,7 @@ "firebase-functions-test": "^3.0.0", "pino-pretty": "^13.0.0", "replicate": "^0.16.1", + "ts-jest": "^29.3.1", "typescript": "^5.5.4" }, "private": true, diff --git a/test/e2e/search.test.ts b/test/e2e/search.test.ts new file mode 100644 index 0000000..51d34c1 --- /dev/null +++ b/test/e2e/search.test.ts @@ -0,0 +1,182 @@ +import { TestHttpClient } from '../utils/http-client'; + +function calculateToken(data: any[], chargeAmountScaler: number) { + const length = data.length; + + return 10000 * Math.ceil(length / 10) * chargeAmountScaler; +} + +describe("Test normal cases", () => { + let httpClient: TestHttpClient; + let baseUri = 'http://localhost:3001'; + const apiKey = process.env.TEST_API_KEY; + + beforeAll(async() => { + expect(apiKey).toBeDefined(); + + httpClient = new TestHttpClient(apiKey!, baseUri); + }); + + afterAll(async() => { + await httpClient.close(); + }); + + it('[without api key]', async() => { + const client = new TestHttpClient('', baseUri); + try { + await client.search({q: 'Jina AI'}, { + headers: { + 'x-no-cache': 'true', + 'X-Respond-With': 'no-content', + 'Accept': 'application/json', + } + }); + } catch (e) { + expect(e).toBeDefined(); + expect(e.code).toEqual(401); + expect(e.message).toEqual('Invalid API key, please get a new one from https://jina.ai'); + } + + }); + + /** + * @description + * Test: + * - no content + * - no fallback + * - json response + * - no links + * - no images + */ + fit("[without content][without fallback][json response]", async () => { + const result = await httpClient.search({q: 'Jina AI'}, { + headers: { + 'x-no-cache': 'true', + 'X-Respond-With': 'no-content', + 'Accept': 'application/json', + } + }); + const data = result.data; + + expect(result.code).toBe(200); + expect(data).toBeDefined(); + expect(data.length).toBeGreaterThan(0); + const firstMatch = data[0]; + expect(firstMatch.title).toBeDefined(); + expect(firstMatch.favicon).toBeUndefined(); + expect(firstMatch.content).toBeUndefined(); + expect(firstMatch.links).toBeUndefined(); + expect(firstMatch.images).toBeUndefined(); + + const tokens = calculateToken(data, 1); + + expect(result.meta).toEqual({ + usage: { + tokens + } + }); + }); + + /** + * @description + * Test: + * - no content + * - with fallback + * - json response + * - no links + * - no images + */ + it('[without content][with fallback][json response]', async() => { + const result = await httpClient.search({q: 'new Jeans Hanni 人格分析 存在口供偏差'}, { + headers: { + 'x-no-cache': 'true', + 'X-Respond-With': 'no-content', + 'Accept': 'application/json', + } + }); + const data = result.data; + + expect(result.code).toBe(200); + expect(data).toBeDefined(); + const tokens = calculateToken(data, 2); + + expect(result.meta).toEqual({ + fallback: 'new Jeans Hanni', + usage: { + tokens + } + }); + }); + + /** + * @description + * Test: + * - with content + * - no fallback + * - text response + * - no links + * - no images + */ + it('[with content][without fallback][text response]', async () => { + const result = await httpClient.search({q: 'Jina AI'}, { + headers: { + 'x-no-cache': 'true', + 'X-Respond-With': 'no-content', + }, + responseType: 'text', + }); + + expect(typeof result).toEqual('string'); + + // extract 'Title' and 'URL Source' + const titleRegex = /Title/g; + const titles = result.matchAll(titleRegex); + const titleArray = Array.from(titles); + + const urlRegex = /URL Source/g; + const urls = result.matchAll(urlRegex); + const urlArray = Array.from(urls); + + expect(titleArray.length).toEqual(urlArray.length); + }); + + + /** + * @description + * Test: + * - with content + * - without fallback + * - json response + * - with favicon + * - with links + * - with images + * + */ + it("[with content][with favicon][without fallback][json response]", async () => { + const result = await httpClient.search({q: 'Jina AI'}, { + headers: { + 'x-no-cache': 'true', + 'Accept': 'application/json', + 'X-With-Favicons': 'true', + 'X-With-Images-Summary': 'true', + 'X-With-Links-Summary': 'true', + } + }); + const data = result.data; + + expect(result.code).toBe(200); + expect(data).toBeDefined(); + expect(data.length).toBeGreaterThan(0); + + const firstMatch = data[0]; + expect(firstMatch.title).toBeDefined(); + expect(firstMatch.favicon).toBeDefined(); + expect(firstMatch.content).toBeDefined(); + expect(Object.keys(firstMatch.links).length).toBeGreaterThan(0); + expect(Object.keys(firstMatch.images).length).toBeGreaterThan(0); + + const tokens = calculateToken(data, 1); + + expect(result.meta.usage.tokens).toBeGreaterThan(tokens) + }); +}); \ No newline at end of file diff --git a/test/jest.config.js b/test/jest.config.js new file mode 100644 index 0000000..950ced9 --- /dev/null +++ b/test/jest.config.js @@ -0,0 +1,30 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} **/ +module.exports = { + testEnvironment: "node", + verbose: true, + preset: 'ts-jest', + moduleFileExtensions: [ + "js", + "ts", + "json", + "node" + ], + transform: { + "^.+\\.tsx?$": ["ts-jest",{ + tsconfig: "/test/tsconfig.test.json", + }], + }, + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + testMatch: [ + "/test/e2e/**/*.test.ts" + ], + setupFilesAfterEnv: [], + testPathIgnorePatterns: [ + "/node_modules/", + "/dist/", + ], + rootDir: '../', + testTimeout: 30000, +}; \ No newline at end of file diff --git a/test/tsconfig.test.json b/test/tsconfig.test.json new file mode 100644 index 0000000..0fbb00d --- /dev/null +++ b/test/tsconfig.test.json @@ -0,0 +1,18 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "module": "CommonJS", + "strict": true, + "noImplicitAny": false, + "types": ["jest", "node"], + "isolatedModules": true, + "esModuleInterop": true, + "allowJs": true, + "skipLibCheck": true, + "useDefineForClassFields": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["../build/**/*", "./**/*"] +} \ No newline at end of file diff --git a/test/utils/http-client.ts b/test/utils/http-client.ts new file mode 100644 index 0000000..86aa8cc --- /dev/null +++ b/test/utils/http-client.ts @@ -0,0 +1,67 @@ +export class TestHttpClient { + + private controller: AbortController | null = null; + + constructor( + public apiKey: string, + public baseUri: string = 'http://localhost:3000' + ) { + this.baseUri = baseUri; + } + + async request(uri: string, payload: any, options: any = {}) { + this.controller = new AbortController(); + + const response = await fetch(`${this.baseUri}${uri}`, { + method: options.method || 'GET', + headers: { + Authorization: `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json', + ...options?.headers, + }, + body: JSON.stringify(payload), + signal: this.controller.signal, + }); + + if (!response.ok) { + let error: any; + try { + error = await response.json(); + } catch (e) { + error = await response.text(); + } + + throw error; + } + + if (options.responseType === 'json') { + return await response.json(); + } + if (options.responseType === 'text') { + return await response.text(); + } + + throw new Error('Unsupported response type'); + } + + async search(payload: any, options: any = {}) { + const data = await this.request('', payload, { + responseType: 'json', + method: 'POST', + ...options, + }); + + return data; + } + + // Add this close method to clean up connections + async close() { + if (this.controller) { + this.controller.abort(); + this.controller = null; + } + + // This helps resolve the open handle issue with Node's fetch + await new Promise(resolve => setTimeout(resolve, 100)); + } +} \ No newline at end of file