From cee9b2cd500077400669236f73b65b14f6d3ddd5 Mon Sep 17 00:00:00 2001 From: Andrey Sitnik Date: Fri, 17 May 2024 15:35:35 +0000 Subject: [PATCH] Fix test coverage --- core/download.ts | 1 + core/environment.ts | 27 ++++---- core/feed.ts | 1 + core/router.ts | 112 +++++++++++++++++----------------- core/slow.ts | 46 +++----------- core/test/download.test.ts | 8 +++ core/test/environment.test.ts | 20 ++++-- core/test/fast.test.ts | 2 - core/test/i18n.test.ts | 16 ++++- core/test/router.test.ts | 10 ++- core/test/slow.test.ts | 14 +++-- core/test/two-steps.test.ts | 2 +- loader-tests/package.json | 2 +- package.json | 2 +- pnpm-lock.yaml | 48 +++++++-------- proxy/package.json | 2 +- proxy/proxy.ts | 20 +++--- proxy/test/proxy.test.ts | 17 ++++++ server/package.json | 2 +- web/package.json | 2 +- 20 files changed, 188 insertions(+), 166 deletions(-) diff --git a/core/download.ts b/core/download.ts index afa8a17a..0dc2246c 100644 --- a/core/download.ts +++ b/core/download.ts @@ -73,6 +73,7 @@ export function createTextResponse( warning('Parse JSON error', e.message) return null } else { + /* c8 ignore next 2 */ throw e } } diff --git a/core/environment.ts b/core/environment.ts index fb63a0c2..0a07923d 100644 --- a/core/environment.ts +++ b/core/environment.ts @@ -72,16 +72,20 @@ let currentEnvironment: Environment | undefined let listeners: EnvironmentListener[] = [] let unbinds: ((() => void) | void)[] = [] -export function onEnvironment(ch: EnvironmentListener): void { +function runEnvListener(listener: EnvironmentListener): void { + let unbind = listener(currentEnvironment!) + if (Array.isArray(unbind)) { + unbinds.push(...unbind) + } else { + unbinds.push(unbind) + } +} + +export function onEnvironment(cb: EnvironmentListener): void { if (currentEnvironment) { - let unbind = ch(currentEnvironment) - if (Array.isArray(unbind)) { - unbinds.push(...unbind) - } else { - unbinds.push(unbind) - } + runEnvListener(cb) } - listeners.push(ch) + listeners.push(cb) } export function setupEnvironment( @@ -104,12 +108,7 @@ export function setupEnvironment( } for (let listener of listeners) { - let unbind = listener(currentEnvironment) - if (Array.isArray(unbind)) { - unbinds.push(...unbind) - } else { - unbinds.push(unbind) - } + runEnvListener(listener) } } diff --git a/core/feed.ts b/core/feed.ts index fcd35537..8be5e742 100644 --- a/core/feed.ts +++ b/core/feed.ts @@ -55,6 +55,7 @@ onMount($hasFeeds, () => { } }) + /* c8 ignore next 4 */ return () => { unbindClient() unbindFeeds?.() diff --git a/core/router.ts b/core/router.ts index 870dcb5c..56b55a47 100644 --- a/core/router.ts +++ b/core/router.ts @@ -5,7 +5,7 @@ import { fastCategories } from './fast.js' import { hasFeeds } from './feed.js' import { computeFrom, readonlyExport } from './lib/stores.js' import { userId } from './settings.js' -import { slowFeeds } from './slow.js' +import { slowCategories } from './slow.js' export interface Routes { about: {} @@ -76,8 +76,17 @@ function redirect(route: ParamlessRouteName | Route): Route { return { ...open(route), redirect: true } } -function isNumber(value: number | string): boolean { - return typeof value === 'number' || /^\d+$/.test(value) +function validateNumber( + value: number | string, + cb: (fixed: number) => Route +): Route { + if (typeof value === 'number') { + return cb(value) + } else if (/^\d+$/.test(value)) { + return cb(parseInt(value)) + } else { + return open('notFound') + } } let $router = atom({ params: {}, route: 'home' }) @@ -87,99 +96,88 @@ export const router = readonlyExport($router) onEnvironment(({ baseRouter }) => { return computeFrom( $router, - [baseRouter, userId, hasFeeds, fastCategories, slowFeeds], - (page, user, withFeeds, fast, slow) => { - if (!page) { + [baseRouter, userId, hasFeeds, fastCategories, slowCategories], + (route, user, withFeeds, fast, slowUnread) => { + if (!route) { return open('notFound') } else if (user) { - if (GUEST.has(page.route) || page.route === 'home') { + if (GUEST.has(route.route) || route.route === 'home') { if (withFeeds) { return redirect({ params: {}, route: 'slow' }) } else { return redirect('welcome') } - } else if (page.route === 'welcome' && withFeeds) { + } else if (route.route === 'welcome' && withFeeds) { return redirect('slow') - } else if (page.route === 'settings') { + } else if (route.route === 'settings') { return redirect('interface') - } else if (page.route === 'feeds') { - return redirect('categories') - } else if (page.route === 'fast') { - if (!page.params.category && !fast.isLoading) { + } else if (route.route === 'feeds') { + return redirect('add') + } else if (route.route === 'fast') { + if (!route.params.category && !fast.isLoading) { return redirect({ params: { category: fast.categories[0].id }, route: 'fast' }) } - if (page.params.category && !fast.isLoading) { + if (route.params.category && !fast.isLoading) { let category = fast.categories.find( - i => i.id === page.params.category + i => i.id === route.params.category ) if (!category) { return open('notFound') } } - if (page.params.since) { - if (isNumber(page.params.since)) { - let since = - typeof page.params.since === 'number' - ? page.params.since - : parseInt(page.params.since) - return open({ + if (route.params.since) { + return validateNumber(route.params.since, since => { + return { + ...route, params: { - ...page.params, + ...route.params, since - }, - route: 'fast' - }) - } else { - return open('notFound') - } - } - } else if (page.route === 'slow') { - if (!page.params.feed && !slow.isLoading) { - return redirect({ - params: { feed: slow.feeds[0]?.id || '', page: 1 }, - route: 'slow' + } + } }) } - if (page.params.feed && !slow.isLoading) { - let feed = slow.feeds.find(f => f.id === page.params.feed) - if (!feed) { - return open('notFound') + } else if (route.route === 'slow') { + if (!route.params.feed && !slowUnread.isLoading) { + let firstCategory = slowUnread.tree[0] + if (firstCategory) { + let feeds = firstCategory[1] + let feedData = feeds[0] + if (feedData) { + return redirect({ + params: { feed: feedData[0].id || '' }, + route: 'slow' + }) + } } } - if (page.params.page) { - if (isNumber(page.params.page)) { - let currentPage = - typeof page.params.page === 'number' - ? page.params.page - : parseInt(page.params.page) - return open({ + if (route.params.page) { + return validateNumber(route.params.page, page => { + return { + ...route, params: { - ...page.params, - page: currentPage - }, - route: 'slow' - }) - } else { - return open('notFound') - } + ...route.params, + page + } + } + }) } else { return open({ params: { - ...page.params, + ...route.params, page: 1 }, route: 'slow' }) } } - } else if (!GUEST.has(page.route)) { + } else if (!GUEST.has(route.route)) { return open('start') } - return page + return route }, (oldRoute, newRoute) => { return ( diff --git a/core/slow.ts b/core/slow.ts index b53022b8..7ea45102 100644 --- a/core/slow.ts +++ b/core/slow.ts @@ -9,9 +9,9 @@ import { } from './category.js' import { client } from './client.js' import { onEnvironment } from './environment.js' -import { BROKEN_FEED, type FeedValue, loadFeed, loadFeeds } from './feed.js' +import { BROKEN_FEED, type FeedValue, loadFeed } from './feed.js' import { listenMany, readonlyExport } from './lib/stores.js' -import { getPost, getPosts, loadPosts, type PostValue } from './post.js' +import { getPost, loadPosts, type PostValue } from './post.js' import { type Route, router } from './router.js' export type SlowCategoriesTree = [CategoryValue, [FeedValue, number][]][] @@ -120,20 +120,8 @@ let $posts = atom({ isLoading: true }) export const slowPosts = readonlyExport($posts) -export type SlowFeedsValue = - | { feeds: FeedValue[]; isLoading: false } - | { isLoading: true } - -let $slowFeeds = atom({ isLoading: true }) - -export const slowFeeds = readonlyExport($slowFeeds) - let POSTS_PER_PAGE = 100 -export function setSlowPostsPerPage(value: number): void { - POSTS_PER_PAGE = value -} - let $totalPosts = atom(0) export const totalSlowPosts = readonlyExport($totalPosts) @@ -175,16 +163,12 @@ let postUnbind: (() => void) | undefined async function load(feedId: string, page: number = 1): Promise { $page.set(page) - let [feedPosts, feeds] = await Promise.all([ - loadPosts({ feedId, reading: 'slow' }), - loadFeeds({ reading: 'slow' }) - ]) + let feedPosts = await loadPosts({ feedId, reading: 'slow' }) let sorted = feedPosts.sort((a, b) => b.publishedAt - a.publishedAt) let fromIndex = (page - 1) * POSTS_PER_PAGE let posts = sorted.slice(fromIndex, fromIndex + POSTS_PER_PAGE) $posts.set({ isLoading: false, list: posts }) - $slowFeeds.set({ feeds, isLoading: false }) $totalPosts.set(feedPosts.length) $totalPages.set(Math.ceil(feedPosts.length / POSTS_PER_PAGE)) } @@ -214,30 +198,14 @@ onEnvironment(({ openRoute }) => { } }), router.listen(page => { - if (notSynced(page) && page.params.feed) { - loadSlowPosts(page.params.feed, page.params.page) - } if (page.route === 'slow') { if (page.params.feed) { - if ($currentFeed.get() !== page.params.feed) { - postsUnbind?.() - postsUnbind = getPosts({ feedId: page.params.feed }).subscribe( - posts => { - if (posts.isLoading) { - $posts.set({ isLoading: true }) - } else { - $posts.set({ isLoading: false, list: posts.list }) - } - } - ) + if (notSynced(page)) { + loadSlowPosts(page.params.feed, page.params.page) } } else { - postsUnbind?.() - postsUnbind = undefined - $posts.set({ - isLoading: false, - list: [] - }) + $currentFeed.set(undefined) + $posts.set({ isLoading: false, list: [] }) } if (page.params.post) { if ($post.get()?.id !== page.params.post) { diff --git a/core/test/download.test.ts b/core/test/download.test.ts index d0abc0ca..01c64aba 100644 --- a/core/test/download.test.ts +++ b/core/test/download.test.ts @@ -220,6 +220,14 @@ test('detects content type', async () => { }).contentType, 'application/custom' ) + equal( + createTextResponse('custom', { + headers: new Headers({ + 'content-type': 'text/plain; charset=utf-8' + }) + }).contentType, + 'text/plain' + ) equal( createTextResponse('', { headers: new Headers({ 'content-type': 'text/html' }) diff --git a/core/test/environment.test.ts b/core/test/environment.test.ts index 87e452dc..45ed9216 100644 --- a/core/test/environment.test.ts +++ b/core/test/environment.test.ts @@ -17,29 +17,39 @@ test('throws on current environment if it is not set', () => { test('runs callback when environment will be set', () => { let calls: string[] = [] - let cleans = 0 + let cleans = '' onEnvironment(env => { calls.push(env.locale.get()) return () => { - cleans += 1 + cleans += '1' } }) + onEnvironment(() => { + return [ + () => { + cleans += '2' + }, + () => { + cleans += '3' + } + ] + }) deepStrictEqual(calls, []) setupEnvironment({ ...getTestEnvironment(), locale: atom('fr') }) deepStrictEqual(calls, ['fr']) - equal(cleans, 0) + equal(cleans, '') setupEnvironment({ ...getTestEnvironment(), locale: atom('ru') }) deepStrictEqual(calls, ['fr', 'ru']) - equal(cleans, 1) + equal(cleans, '123') let after: string[] = [] onEnvironment(env => { after.push(env.locale.get()) }) deepStrictEqual(after, ['ru']) - equal(cleans, 1) + equal(cleans, '123') }) test('returns current environment', () => { diff --git a/core/test/fast.test.ts b/core/test/fast.test.ts index 618fe9b7..018fb5df 100644 --- a/core/test/fast.test.ts +++ b/core/test/fast.test.ts @@ -477,8 +477,6 @@ test('tracks current post', async () => { route: 'fast' }) equal(openedFastPost.get()?.id, post3) - equal(openedFastPost.get()?.isLoading, true) - await setTimeout(10) equal(openedFastPost.get()?.isLoading, false) equal((openedFastPost.get() as PostValue).title, '3') }) diff --git a/core/test/i18n.test.ts b/core/test/i18n.test.ts index f71cbb81..1ad6bdad 100644 --- a/core/test/i18n.test.ts +++ b/core/test/i18n.test.ts @@ -1,11 +1,25 @@ import './environment.js' -import { equal } from 'node:assert' +import { atom } from 'nanostores' +import { deepStrictEqual, equal } from 'node:assert' import { test } from 'node:test' import { i18n, settingsMessages } from '../index.js' +import { enableClientTest } from './utils.js' test('has i18n', async () => { equal(typeof i18n, 'function') equal(typeof settingsMessages.get().theme, 'string') + + let locale = atom('en') + let loading: string[] = [] + enableClientTest({ + locale, + async translationLoader(lang) { + loading.push(lang) + return {} + } + }) + locale.set('es') + deepStrictEqual(loading, ['es']) }) diff --git a/core/test/router.test.ts b/core/test/router.test.ts index 2be0d3f3..186ee811 100644 --- a/core/test/router.test.ts +++ b/core/test/router.test.ts @@ -125,14 +125,22 @@ test('transforms routers for users with feeds', async () => { }) }) -test('transforms settings to first settings page', () => { +test('transforms section to first section page', () => { userId.set('10') + setBaseTestRoute({ params: {}, route: 'settings' }) deepStrictEqual(router.get(), { params: {}, redirect: true, route: 'interface' }) + + setBaseTestRoute({ params: {}, route: 'feeds' }) + deepStrictEqual(router.get(), { + params: {}, + redirect: true, + route: 'add' + }) }) test('transforms routers to first fast category', async () => { diff --git a/core/test/slow.test.ts b/core/test/slow.test.ts index 936fe091..cd7cebb4 100644 --- a/core/test/slow.test.ts +++ b/core/test/slow.test.ts @@ -35,8 +35,6 @@ afterEach(async () => { }) test('has empty slow categories from beginning', async () => { - deepStrictEqual(slowCategories.get(), { isLoading: true }) - await setTimeout(100) deepStrictEqual(slowCategories.get(), { isLoading: false, tree: [] @@ -128,7 +126,7 @@ test('loads posts from URL', async () => { deepStrictEqual(openedSlowPost.get(), undefined) setBaseTestRoute({ params: {}, route: 'slow' }) - deepStrictEqual(slowPosts.get(), { isLoading: false, list: [] }) + deepStrictEqual(slowPosts.get(), { isLoading: true }) deepStrictEqual(openedSlowPost.get(), undefined) setBaseTestRoute({ params: { feed: feedA }, route: 'slow' }) @@ -170,10 +168,16 @@ test('loads posts from URL', async () => { }) deepStrictEqual(openedSlowPost.get(), undefined) - setBaseTestRoute({ params: {}, route: 'slow' }) + setBaseTestRoute({ params: { feed: feedB }, route: 'slow' }) + deepStrictEqual(slowPosts.get(), { isLoading: true }) + + await setTimeout(10) deepStrictEqual(slowPosts.get(), { isLoading: false, - list: [await loadPost(postA1), await loadPost(postA2)] + list: [await loadPost(postB)] }) + + setBaseTestRoute({ params: {}, route: 'slow' }) + deepStrictEqual(slowPosts.get(), { isLoading: true }) deepStrictEqual(openedSlowPost.get(), undefined) }) diff --git a/core/test/two-steps.test.ts b/core/test/two-steps.test.ts index 3e638d61..fe50084f 100644 --- a/core/test/two-steps.test.ts +++ b/core/test/two-steps.test.ts @@ -78,7 +78,7 @@ test('works with slow route', async () => { secondStep.set(false) let idA = await addCategory({ title: 'A' }) let feed = await addFeed(testFeed({ categoryId: idA, reading: 'slow' })) - let post = await addPost(testPost({ feedId: feed })) + let post = await addPost(testPost({ feedId: feed, reading: 'slow' })) setBaseTestRoute({ params: { feed }, route: 'slow' }) await setTimeout(100) diff --git a/loader-tests/package.json b/loader-tests/package.json index ce575cd5..ccce5e36 100644 --- a/loader-tests/package.json +++ b/loader-tests/package.json @@ -17,7 +17,7 @@ "@slowreader/core": "link:../core", "jsdom": "24.0.0", "nanostores": "0.10.3", - "tsx": "4.10.2", + "tsx": "4.10.4", "yaml": "2.4.2" } } diff --git a/package.json b/package.json index ddddfd74..bf7f3be2 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "svelte": "4.2.17", "svelte-check": "3.7.1", "svgo": "3.3.2", - "tsx": "4.10.2", + "tsx": "4.10.4", "typescript": "5.4.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65132b49..dbca36dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,13 +85,13 @@ importers: version: 4.2.17 svelte-check: specifier: 3.7.1 - version: 3.7.1(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.2))(postcss@8.4.38)(svelte@4.2.17) + version: 3.7.1(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.4))(postcss@8.4.38)(svelte@4.2.17) svgo: specifier: 3.3.2 version: 3.3.2 tsx: - specifier: 4.10.2 - version: 4.10.2 + specifier: 4.10.4 + version: 4.10.4 typescript: specifier: 5.4.5 version: 5.4.5 @@ -162,8 +162,8 @@ importers: specifier: 0.10.3 version: 0.10.3 tsx: - specifier: 4.10.2 - version: 4.10.2 + specifier: 4.10.4 + version: 4.10.4 yaml: specifier: 2.4.2 version: 2.4.2 @@ -181,8 +181,8 @@ importers: specifier: 9.1.0 version: 9.1.0 tsx: - specifier: 4.10.2 - version: 4.10.2 + specifier: 4.10.4 + version: 4.10.4 server: dependencies: @@ -194,8 +194,8 @@ importers: version: link:../api devDependencies: tsx: - specifier: 4.10.2 - version: 4.10.2 + specifier: 4.10.4 + version: 4.10.4 web: dependencies: @@ -258,10 +258,10 @@ importers: version: 4.2.17 svelte-preprocess: specifier: 5.1.4 - version: 5.1.4(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.2))(postcss@8.4.38)(svelte@4.2.17)(typescript@5.4.5) + version: 5.1.4(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.4))(postcss@8.4.38)(svelte@4.2.17)(typescript@5.4.5) tsx: - specifier: 4.10.2 - version: 4.10.2 + specifier: 4.10.4 + version: 4.10.4 vite: specifier: 5.2.11 version: 5.2.11(@types/node@20.12.12) @@ -286,7 +286,7 @@ importers: version: 8.1.1(prettier@3.2.5)(svelte@4.2.17) '@storybook/svelte-vite': specifier: 8.1.1 - version: 8.1.1(@babel/core@7.24.5)(@sveltejs/vite-plugin-svelte@3.1.0(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)))(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.2))(postcss@8.4.38)(prettier@3.2.5)(svelte@4.2.17)(typescript@5.4.5)(vite@5.2.11(@types/node@20.12.12)) + version: 8.1.1(@babel/core@7.24.5)(@sveltejs/vite-plugin-svelte@3.1.0(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)))(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.4))(postcss@8.4.38)(prettier@3.2.5)(svelte@4.2.17)(typescript@5.4.5)(vite@5.2.11(@types/node@20.12.12)) '@stylistic/stylelint-config': specifier: 1.0.1 version: 1.0.1(stylelint@16.5.0(typescript@5.4.5)) @@ -5370,8 +5370,8 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tsx@4.10.2: - resolution: {integrity: sha512-gOfACgv1ElsIjvt7Fp0rMJKGnMGjox0JfGOfX3kmZCV/yZumaNqtHGKBXt1KgaYS9KjDOmqGeI8gHk/W7kWVZg==} + tsx@4.10.4: + resolution: {integrity: sha512-Gtg9qnZWNqC/OtcgiXfoAUdAKx3/cgKOYvEocAsv+m21MV/eKpV/WUjRXe6/sDCaGBl2/v8S6v29BpUnGMCX5A==} engines: {node: '>=18.0.0'} hasBin: true @@ -7465,7 +7465,7 @@ snapshots: memoizerific: 1.11.3 qs: 6.12.1 - '@storybook/svelte-vite@8.1.1(@babel/core@7.24.5)(@sveltejs/vite-plugin-svelte@3.1.0(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)))(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.2))(postcss@8.4.38)(prettier@3.2.5)(svelte@4.2.17)(typescript@5.4.5)(vite@5.2.11(@types/node@20.12.12))': + '@storybook/svelte-vite@8.1.1(@babel/core@7.24.5)(@sveltejs/vite-plugin-svelte@3.1.0(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)))(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.4))(postcss@8.4.38)(prettier@3.2.5)(svelte@4.2.17)(typescript@5.4.5)(vite@5.2.11(@types/node@20.12.12))': dependencies: '@storybook/builder-vite': 8.1.1(prettier@3.2.5)(typescript@5.4.5)(vite@5.2.11(@types/node@20.12.12)) '@storybook/node-logger': 8.1.1 @@ -7474,7 +7474,7 @@ snapshots: '@sveltejs/vite-plugin-svelte': 3.1.0(svelte@4.2.17)(vite@5.2.11(@types/node@20.12.12)) magic-string: 0.30.10 svelte: 4.2.17 - svelte-preprocess: 5.1.4(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.2))(postcss@8.4.38)(svelte@4.2.17)(typescript@5.4.5) + svelte-preprocess: 5.1.4(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.4))(postcss@8.4.38)(svelte@4.2.17)(typescript@5.4.5) sveltedoc-parser: 4.2.1 ts-dedent: 2.2.0 vite: 5.2.11(@types/node@20.12.12) @@ -10675,13 +10675,13 @@ snapshots: optionalDependencies: postcss: 8.4.38 - postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.2): + postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.4): dependencies: lilconfig: 3.1.1 yaml: 2.4.2 optionalDependencies: postcss: 8.4.38 - tsx: 4.10.2 + tsx: 4.10.4 optional: true postcss-nesting@12.1.4(postcss@8.4.38): @@ -11489,7 +11489,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.7.1(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.2))(postcss@8.4.38)(svelte@4.2.17): + svelte-check@3.7.1(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.4))(postcss@8.4.38)(svelte@4.2.17): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 @@ -11498,7 +11498,7 @@ snapshots: picocolors: 1.0.1 sade: 1.8.1 svelte: 4.2.17 - svelte-preprocess: 5.1.4(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.2))(postcss@8.4.38)(svelte@4.2.17)(typescript@5.4.5) + svelte-preprocess: 5.1.4(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.4))(postcss@8.4.38)(svelte@4.2.17)(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: - '@babel/core' @@ -11525,7 +11525,7 @@ snapshots: dependencies: svelte: 4.2.17 - svelte-preprocess@5.1.4(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.2))(postcss@8.4.38)(svelte@4.2.17)(typescript@5.4.5): + svelte-preprocess@5.1.4(@babel/core@7.24.5)(postcss-load-config@5.1.0(postcss@8.4.38)(tsx@4.10.4))(postcss@8.4.38)(svelte@4.2.17)(typescript@5.4.5): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -11536,7 +11536,7 @@ snapshots: optionalDependencies: '@babel/core': 7.24.5 postcss: 8.4.38 - postcss-load-config: 5.1.0(postcss@8.4.38)(tsx@4.10.2) + postcss-load-config: 5.1.0(postcss@8.4.38)(tsx@4.10.4) typescript: 5.4.5 svelte@4.2.17: @@ -11695,7 +11695,7 @@ snapshots: tslib@2.6.2: {} - tsx@4.10.2: + tsx@4.10.4: dependencies: esbuild: 0.20.2 get-tsconfig: 4.7.5 diff --git a/proxy/package.json b/proxy/package.json index bcaf7af3..6bd60f71 100644 --- a/proxy/package.json +++ b/proxy/package.json @@ -21,6 +21,6 @@ "devDependencies": { "better-node-test": "0.5.1", "c8": "9.1.0", - "tsx": "4.10.2" + "tsx": "4.10.4" } } diff --git a/proxy/proxy.ts b/proxy/proxy.ts index fbd3c510..e6aaca8d 100644 --- a/proxy/proxy.ts +++ b/proxy/proxy.ts @@ -23,17 +23,14 @@ export function createProxyServer(config: { let sent = false try { - if (!req.url) { - throw new BadRequestError('Bad URL') - } - let url = decodeURIComponent(req.url.slice(1)) + let url = decodeURIComponent((req.url ?? '').slice(1)) + let parsedUrl: URL try { - new URL(url) + parsedUrl = new URL(url) } catch { throw new BadRequestError('Invalid URL') } - let parsedUrl = new URL(url) // Only HTTP or HTTPS protocols are allowed if (!url.startsWith('http://') && !url.startsWith('https://')) { @@ -92,10 +89,8 @@ export function createProxyServer(config: { }) sent = true - if (!targetResponse.body) { - res.end() - } else { - let size = 0 + let size = 0 + if (targetResponse.body) { let reader = targetResponse.body.getReader() let chunk: ReadableStreamReadResult do { @@ -108,8 +103,8 @@ export function createProxyServer(config: { } } } while (!chunk.done) - res.end() } + res.end() } catch (e) { // Known errors if (e instanceof Error && e.name === 'TimeoutError') { @@ -122,7 +117,8 @@ export function createProxyServer(config: { return } - // Unknown or internal errors + // Unknown or Internal errors + /* c8 ignore next 9 */ if (e instanceof Error) { process.stderr.write(styleText('red', e.stack ?? e.message) + '\n') } else if (typeof e === 'string') { diff --git a/proxy/test/proxy.test.ts b/proxy/test/proxy.test.ts index d6be8c1b..0d6cd83b 100644 --- a/proxy/test/proxy.test.ts +++ b/proxy/test/proxy.test.ts @@ -32,6 +32,9 @@ let target = createServer(async (req, res) => { res.write('a'.repeat(150)) await setTimeout(10) res.write('a'.repeat(150)) + } else if (queryParams.error) { + res.writeHead(500) + res.end('Error') } else { res.writeHead(200, { 'Content-Type': 'text/json', @@ -117,6 +120,14 @@ test('can use only GET ', async () => { equal(await response.text(), 'Only GET is allowed') }) +test('checks URL', async () => { + let response1 = await fetch(`${proxyUrl}/bad`, {}) + await expectBadRequest(response1, 'Invalid URL') + + let response2 = await fetch(proxyUrl, {}) + await expectBadRequest(response2, 'Invalid URL') +}) + test('can use only HTTP or HTTPS protocols', async () => { let response = await fetch( `${proxyUrl}/${targetUrl.replace('http', 'ftp')}`, @@ -181,3 +192,9 @@ test('checks response size', async () => { let body2 = await response2.text() equal(body2.length, 150) }) + +test('is ready for errors', async () => { + let response1 = await request(targetUrl + '?error=1', {}) + equal(response1.status, 500) + equal(await response1.text(), 'Error') +}) diff --git a/server/package.json b/server/package.json index 6b994ec8..881790f2 100644 --- a/server/package.json +++ b/server/package.json @@ -14,6 +14,6 @@ "@slowreader/api": "link:../api" }, "devDependencies": { - "tsx": "4.10.2" + "tsx": "4.10.4" } } diff --git a/web/package.json b/web/package.json index bbf423c9..befff2fc 100644 --- a/web/package.json +++ b/web/package.json @@ -44,7 +44,7 @@ "rollup": "4.17.2", "svelte": "4.2.17", "svelte-preprocess": "5.1.4", - "tsx": "4.10.2", + "tsx": "4.10.4", "vite": "5.2.11" }, "devDependencies": {