From 8b1f641fca79ad580bcb6d94f41ad0f52c495e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Marseault?= <22910497+Larsluph@users.noreply.github.com> Date: Wed, 8 Nov 2023 19:06:11 +0100 Subject: [PATCH] perf(faker): Improve torrent mocking (#1187) --- __mocks__/torrents.json | 8 ++ .../Settings/VueTorrent/General.vue | 7 +- src/composables/TorrentBuilder.ts | 104 ++++++++++++--- src/helpers/text.spec.ts | 19 ++- src/helpers/text.ts | 5 + src/stores/maindata.ts | 14 +- src/types/vuetorrent/Torrent.ts | 1 + src/utils/faker.ts | 121 ------------------ vite.config.ts | 11 +- 9 files changed, 137 insertions(+), 153 deletions(-) create mode 100644 __mocks__/torrents.json delete mode 100644 src/utils/faker.ts diff --git a/__mocks__/torrents.json b/__mocks__/torrents.json new file mode 100644 index 00000000..f57645a2 --- /dev/null +++ b/__mocks__/torrents.json @@ -0,0 +1,8 @@ +[ + { + "name": "First torrent" + }, + { + "name": "Second torrent" + } +] \ No newline at end of file diff --git a/src/components/Settings/VueTorrent/General.vue b/src/components/Settings/VueTorrent/General.vue index ade4e263..d71e6c75 100644 --- a/src/components/Settings/VueTorrent/General.vue +++ b/src/components/Settings/VueTorrent/General.vue @@ -26,9 +26,6 @@ const paginationSizes = ref([ 50 ]) -const isProduction = computed(() => process.env.NODE_ENV === 'production') -const isDevelopment = computed(() => process.env.NODE_ENV === 'development') - const theme = computed({ get() { if (vueTorrentStore.matchSystemTheme) return 'auto' @@ -51,9 +48,9 @@ const themeOptions = [ ] const vueTorrentVersion = computed(() => { - if (isProduction.value) { + if (import.meta.env.PROD) { return 'import.meta.env.VITE_PACKAGE_VERSION' - } else if (isDevelopment.value) { + } else if (import.meta.env.DEV) { return 'DEV' } diff --git a/src/composables/TorrentBuilder.ts b/src/composables/TorrentBuilder.ts index 1d9397ca..a16107cf 100644 --- a/src/composables/TorrentBuilder.ts +++ b/src/composables/TorrentBuilder.ts @@ -1,15 +1,24 @@ +import { FilePriority, TorrentState } from '@/constants/qbit' import { formatEta, getDomainBody } from '@/helpers' -import { Torrent } from '@/types/vuetorrent' +import { useMaindataStore } from '@/stores/maindata.ts' import { Torrent as QbitTorrent } from '@/types/qbit/models' +import { Torrent } from '@/types/vuetorrent' +import { faker } from '@faker-js/faker' +import { computed } from 'vue' import { useI18n } from 'vue-i18n' +type StaticTorrent = Omit + export function useTorrentBuilder() { const { t } = useI18n() + const maindataStore = useMaindataStore() + + const categories = computed(() => maindataStore.categories.map(value => value.name) || ['ISO', 'Other', 'Movie', 'Music', 'TV']) const computedValues = ['avgDownloadSpeed', 'avgUploadSpeed', 'globalSpeed', 'globalVolume', 'priority'] function buildFromQbit(data: QbitTorrent): Torrent { - const torrent = { + return buildTorrent({ added_on: data.added_on, amount_left: data.amount_left, auto_tmm: data.auto_tmm, @@ -47,7 +56,7 @@ export function useTorrentBuilder() { seq_dl: data.seq_dl, size: data.size, state: data.state, - stateString: t(`torrent.state.${data.state}`), + stateString: t(`torrent.state.${ data.state }`), super_seeding: data.super_seeding, tags: data.tags.length > 0 ? data.tags.split(', ').map(t => t.trim()) : [], time_active: data.time_active, @@ -59,21 +68,82 @@ export function useTorrentBuilder() { uploaded: data.uploaded, uploaded_session: data.uploaded_session, upspeed: data.upspeed - } - - const dlDuration = torrent.time_active - torrent.seeding_time - const ulDuration = torrent.time_active - - return Object.freeze({ - ...torrent, - // const qlonglong dlDuration = torrent->activeTime() - torrent->finishedTime(); - // dataDict[KEY_PROP_DL_SPEED_AVG] = torrent->totalDownload() / ((dlDuration == 0) ? -1 : dlDuration); - avgDownloadSpeed: torrent.downloaded / ((dlDuration == 0) ? -1 : dlDuration), - avgUploadSpeed: torrent.uploaded / ((ulDuration == 0) ? -1 : ulDuration), - globalSpeed: torrent.dlspeed + torrent.upspeed, - globalVolume: torrent.downloaded + torrent.uploaded }) } - return { computedValues, buildFromQbit } + function buildFromFaker(data: Partial, index: number): Torrent { + const added_on = data.added_on || faker.date.past().getTime() + const available_peers = data.available_peers || faker.number.int({ min: 0, max: 250 }) + const available_seeds = data.available_seeds || faker.number.int({ min: 0, max: 250 }) + const state = data.state || faker.helpers.arrayElement(Object.values(TorrentState)) + const total_size = data.total_size || faker.number.int({ min: 1000, max: 1_000_000_000_000 }) // [1 ko; 1 To] + const tracker = data.tracker || faker.internet.url() + + return buildTorrent({ + added_on, + amount_left: data.amount_left || faker.number.int({ min: 0, max: total_size }), + auto_tmm: data.auto_tmm || faker.datatype.boolean(), + availability: data.availability || faker.number.float({ min: 0, max: 100, precision: 0.01 }), + available_peers, + available_seeds, + category: data.category || faker.helpers.arrayElement(categories.value), + completed_on: data.completed_on || faker.date.between({ from: added_on, to: Date.now() }), + content_path: data.content_path || faker.system.filePath(), + dl_limit: data.dl_limit || faker.number.float({ min: 0, max: 1, precision: 0.01 }), + dlspeed: data.dlspeed || faker.number.int({ min: 0, max: 5000000 }), + download_path: data.download_path || faker.system.filePath(), + downloaded: data.downloaded || faker.number.float({ min: 0, max: 1, precision: 0.01 }), + downloaded_session: data.downloaded_session || faker.number.float({ min: 0, max: 1, precision: 0.01 }), + eta: data.eta || formatEta(faker.number.int({ min: 0, max: 900000 })), + forced: data.forced || faker.datatype.boolean(), + force_start: data.force_start || faker.datatype.boolean(), + hash: data.hash || faker.string.uuid(), + infohash_v1: data.infohash_v1 || faker.string.uuid(), + infohash_v2: data.infohash_v2 || faker.string.uuid(), + last_activity: data.last_activity || faker.number.int({ min: 0, max: 50 }), + magnet: data.magnet_uri || faker.internet.url(), + name: data.name || `Torrent ${ index + 1 }`, + num_leechs: data.num_leechs || faker.number.int(available_peers), + num_seeds: data.num_seeds || faker.number.int(available_seeds), + priority: data.priority || FilePriority.NORMAL, + progress: data.progress || faker.number.float({ min: 0, max: 1, precision: 0.01 }), + ratio: data.ratio || faker.number.float({ min: 0, max: 5, precision: 0.01 }), + ratio_limit: data.ratio_limit || faker.number.float({ min: 0, max: 4, precision: 0.01 }), + ratio_time_limit: data.ratio_time_limit || faker.number.float({ min: 0, max: 4, precision: 0.01 }), + savePath: data.savePath || faker.system.filePath(), + seeding_time: data.seeding_time || faker.number.int({ min: 0, max: 50 }), + seen_complete: data.seen_complete || faker.number.int({ min: 0, max: 50 }), + seq_dl: data.seq_dl || faker.datatype.boolean(), + size: data.size || faker.number.int({ min: 1000, max: total_size }), + state, + stateString: t(`torrent.state.${ state }`), + super_seeding: data.super_seeding || faker.datatype.boolean(), + tags: data.tags || '', + time_active: data.time_active || faker.number.int({ min: 1000, max: 900000 }), + total_size, + tracker, + tracker_domain: getDomainBody(tracker), + trackers_count: data.trackers_count || faker.number.int({ min: 1, max: 50 }), + up_limit: data.up_limit || faker.number.int({ min: 1000, max: 900000 }), + uploaded: data.uploaded || faker.number.int({ min: 1000, max: 900000 }), + uploaded_session: data.uploaded_session || faker.number.int({ min: 1000, max: 900000 }), + upspeed: data.upspeed || faker.number.int({ min: 0, max: 5000000 }) + }) + } + + function buildTorrent(data: StaticTorrent): Torrent { + const dlDuration = data.time_active - data.seeding_time + const ulDuration = data.time_active + + // @ts-expect-error: Type is missing the following properties from type 'Torrent': ... + return Object.freeze({ + ...data, + avgDownloadSpeed: data.downloaded / ((dlDuration == 0) ? -1 : dlDuration), + avgUploadSpeed: data.uploaded / ((ulDuration == 0) ? -1 : ulDuration), + globalSpeed: data.dlspeed + data.upspeed, + globalVolume: data.downloaded + data.uploaded + }) + } + + return { computedValues, buildFromQbit, buildFromFaker } } diff --git a/src/helpers/text.spec.ts b/src/helpers/text.spec.ts index d1da81cd..9325cf2f 100644 --- a/src/helpers/text.spec.ts +++ b/src/helpers/text.spec.ts @@ -1,4 +1,13 @@ -import { capitalize, codeToFlag, extractHostname, getDomainBody, splitByUrl, stringContainsUrl, titleCase } from './text' +import { + capitalize, + codeToFlag, + extractHostname, + getDomainBody, + splitByUrl, + stringContainsUrl, + titleCase, + uuidFromRaw +} from './text' import { expect, test } from 'vitest' test('helpers/text/titleCase', () => { @@ -68,3 +77,11 @@ test('helpers/text/codeToFlag', () => { expect(codeToFlag('it').char).toBe('🇮🇹') expect(codeToFlag('it').url).toBe('https://cdn.jsdelivr.net/npm/twemoji/2/svg/1f1ee-1f1f9.svg') }) + +test('helpers/text/uuidFromRaw', () => { + expect(uuidFromRaw(0n)).toBe('00000000-0000-0000-0000-000000000000') + expect(uuidFromRaw(1n)).toBe('00000000-0000-0000-0000-000000000001') + expect(uuidFromRaw(0xAAAAAn)).toBe('00000000-0000-0000-0000-0000000aaaaa') + expect(uuidFromRaw(0x00000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn)).toBe('ffffffff-ffff-ffff-ffff-ffffffffffff') + expect(uuidFromRaw(0xABCDEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn)).toBe('ffffffff-ffff-ffff-ffff-ffffffffffff') +}) diff --git a/src/helpers/text.ts b/src/helpers/text.ts index 8c56fedb..1a6917e1 100644 --- a/src/helpers/text.ts +++ b/src/helpers/text.ts @@ -83,3 +83,8 @@ export function codeToFlag(code: string) { url } } + +export function uuidFromRaw(bits: bigint) { + let bitString = bits.toString(16).slice(-32).padStart(32, "0"); + return `${bitString.slice(-32, -24)}-${bitString.slice(-24, -20)}-${bitString.slice(-20, -16)}-${bitString.slice(-16, -12)}-${bitString.slice(-12)}`; +} diff --git a/src/stores/maindata.ts b/src/stores/maindata.ts index 1db9d1fb..6622e72a 100644 --- a/src/stores/maindata.ts +++ b/src/stores/maindata.ts @@ -2,6 +2,7 @@ import { useTorrentBuilder } from '@/composables' import { FilePriority, TorrentState } from '@/constants/qbit' import { SortOptions } from '@/constants/qbit/SortOptions' import { extractHostname } from '@/helpers' +import { uuidFromRaw } from '@/helpers/text' import { qbit } from '@/services' import { useAuthStore } from '@/stores/auth' import { useDashboardStore } from '@/stores/dashboard' @@ -10,12 +11,9 @@ import { useVueTorrentStore } from '@/stores/vuetorrent' import { Category, ServerState } from '@/types/qbit/models' import { AddTorrentPayload } from '@/types/qbit/payloads' import { Torrent } from '@/types/vuetorrent' -import { generateMultiple } from '@/utils/faker' import { defineStore } from 'pinia' import { computed, MaybeRefOrGetter, ref, toValue } from 'vue' -const isProduction = computed(() => process.env.NODE_ENV === 'production') - export const useMaindataStore = defineStore('maindata', () => { const categories = ref([]) const isUpdatingMaindata = ref(false) @@ -181,9 +179,13 @@ export const useMaindataStore = defineStore('maindata', () => { // update torrents torrents.value = data.map(t => torrentBuilder.buildFromQbit(t)) - if (!isProduction.value && import.meta.env.VITE_USE_FAKE_TORRENTS === 'true') { - const count = import.meta.env.VITE_FAKE_TORRENT_COUNT - torrents.value.push(...generateMultiple(count).map(t => torrentBuilder.buildFromQbit(t))) + if (import.meta.env.DEV && import.meta.env.VITE_USE_FAKE_TORRENTS === 'true') { + const count = Number(import.meta.env.VITE_FAKE_TORRENT_COUNT) + const fakeTorrents: Partial = (await import('../../__mocks__/torrents.json')).default + + for (let i = 1; i <= count; i++) { + torrents.value.push(torrentBuilder.buildFromFaker({ ...fakeTorrents.at(i), hash: uuidFromRaw(BigInt(i)) }, i)) + } } // filter out deleted torrents from selection diff --git a/src/types/vuetorrent/Torrent.ts b/src/types/vuetorrent/Torrent.ts index f8e8190b..94d9a41d 100644 --- a/src/types/vuetorrent/Torrent.ts +++ b/src/types/vuetorrent/Torrent.ts @@ -38,6 +38,7 @@ export default interface Torrent { seq_dl: boolean size: number state: TorrentState + stateString: string super_seeding: boolean tags: string[] time_active: number diff --git a/src/utils/faker.ts b/src/utils/faker.ts deleted file mode 100644 index 1c1be3ef..00000000 --- a/src/utils/faker.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { faker } from '@faker-js/faker' -import type Torrent from '@/types/qbit/models/Torrent' -import { TorrentState } from '@/constants/qbit/TorrentState' -import { FilePriority } from '@/constants/qbit/FilePriority' - -export function generateMultiple(count: number = 1): Torrent[] { - const torrents: Torrent[] = [] - for (let i = 0; i < count; i++) { - torrents.push( - generateTorrent({ - name: 'Torrent - ' + i - }) - ) - } - return torrents -} - -export function generateTorrent(data: Partial): Torrent { - return { - /** Time (Unix Epoch) when the torrent was added to the client */ - added_on: faker.date.recent().getTime(), - /** Amount of data left to download (bytes) */ - amount_left: faker.number.float({ min: 0, max: 1, precision: 0.01 }), - /** Whether this torrent is managed by Automatic Torrent Management */ - auto_tmm: faker.datatype.boolean(), - /** Percentage of file pieces currently available */ - availability: faker.number.float({ min: 0, max: 1, precision: 0.01 }), - /** Category of the torrent */ - category: faker.helpers.arrayElement(['ISO', 'Other', 'Movie', 'Music', 'TV']), - /** Amount of transfer data completed (bytes) */ - completed: faker.number.float({ min: 0, max: 1, precision: 0.01 }), - /** Time (Unix Epoch) when the torrent completed */ - completion_on: faker.number.float({ min: 0, max: 1, precision: 0.01 }), - /** Absolute path of torrent content (root path for multifile torrents, absolute file path for singlefile torrents) */ - content_path: faker.system.filePath(), - /** Torrent download speed limit (bytes/s). -1 if unlimited. */ - dl_limit: faker.number.float({ min: 0, max: 1, precision: 0.01 }), - /** Torrent download speed (bytes/s) */ - dlspeed: faker.number.int({ min: 0, max: 5000000 }), - /** TODO */ - download_path: faker.system.filePath(), - /** Amount of data downloaded */ - downloaded: faker.number.float({ min: 0, max: 1, precision: 0.01 }), - /** Amount of data downloaded this session */ - downloaded_session: faker.number.float({ min: 0, max: 1, precision: 0.01 }), - /** Torrent ETA (seconds) */ - eta: faker.number.int({ min: 1000, max: 900000 }), - /** True if first last piece are prioritized */ - f_l_piece_prio: faker.datatype.boolean(), - /** True if force start is enabled for this torrent */ - force_start: faker.datatype.boolean(), - /** Torrent hash */ - hash: faker.string.uuid(), - inactive_seeding_time_limit: faker.number.int({ min: 0, max: 50 }), - /** TODO */ - infohash_v1: faker.string.uuid(), - /** TODO */ - infohash_v2: faker.string.uuid(), - /** Last time (Unix Epoch) when a chunk was downloaded/uploaded */ - last_activity: faker.number.int({ min: 0, max: 50 }), - /** Magnet URI corresponding to this torrent */ - magnet_uri: faker.internet.url(), - max_inactive_seeding_time: faker.number.int({ min: 0, max: 50 }), - /** Maximum share ratio until torrent is stopped from seeding/uploading */ - max_ratio: faker.number.float({ min: 0, max: 1, precision: 0.01 }), - /** Maximum seeding time (seconds) until torrent is stopped from seeding */ - max_seeding_time: faker.number.int({ min: 0, max: 50 }), - /** Torrent name */ - name: data.name || faker.animal.dog(), - /** Number of seeds in the swarm */ - num_complete: faker.number.int({ min: 0, max: 50 }), - /** Number of leechers in the swarm */ - num_incomplete: faker.number.int({ min: 0, max: 50 }), - /** Number of leechers connected to */ - num_leechs: faker.number.int({ min: 0, max: 50 }), - /** Number of seeds connected to */ - num_seeds: faker.number.int({ min: 0, max: 50 }), - /** Torrent priority. Returns -1 if queuing is disabled or torrent is in seed mode */ - priority: FilePriority.NORMAL, - /** Torrent progress (percentage/100) */ - progress: faker.number.float({ min: 0, max: 1, precision: 0.01 }), - /** Torrent share ratio. Max ratio value: 9999. */ - ratio: faker.number.float({ min: 0, max: 5, precision: 0.01 }), - /** TODO */ - ratio_limit: faker.number.float({ min: 0, max: 4, precision: 0.01 }), - /** Path where this torrent's data is stored */ - save_path: faker.system.filePath(), - /** Torrent elapsed time while complete (seconds) */ - seeding_time: faker.number.int({ min: 0, max: 50 }), - /** TODO */ - seeding_time_limit: faker.number.int({ min: 0, max: 50 }), - /** Time (Unix Epoch) when this torrent was last seen complete */ - seen_complete: faker.number.int({ min: 0, max: 50 }), - /** True if sequential download is enabled */ - seq_dl: faker.datatype.boolean(), - /** Total size (bytes) of files selected for download */ - size: faker.number.int({ min: 1000000, max: 50000000000 }), - /** Torrent state. See table here below for the possible values */ - state: faker.helpers.arrayElement(Object.values(TorrentState)), - /** True if super seeding is enabled */ - super_seeding: faker.datatype.boolean(), - /** Comma-concatenated tag list of the torrent */ - tags: '', - /** Total active time (seconds) */ - time_active: faker.number.int({ min: 1000, max: 900000 }), - /** Total size (bytes) of all file in this torrent (including unselected ones) */ - total_size: faker.number.int({ min: 1000, max: 900000 }), - /** The first tracker with working status. Returns empty string if no tracker is working. */ - tracker: faker.animal.cat(), - /** TODO */ - trackers_count: faker.number.int({ min: 1000, max: 900000 }), - /** Torrent upload speed limit (bytes/s). -1 if unlimited. */ - up_limit: faker.number.int({ min: 1000, max: 900000 }), - /** Amount of data uploaded */ - uploaded: faker.number.int({ min: 1000, max: 900000 }), - /** Amount of data uploaded this session */ - uploaded_session: faker.number.int({ min: 1000, max: 900000 }), - /** Torrent upload speed (bytes/s) */ - upspeed: faker.number.int({ min: 0, max: 5000000 }) - } -} diff --git a/vite.config.ts b/vite.config.ts index 29658a5c..287371c2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,12 +7,12 @@ import { VitePWA } from 'vite-plugin-pwa' import { resolve } from 'node:path' // https://vitejs.dev/config/ -export default defineConfig(({ mode }) => { +export default defineConfig(({ command, mode }) => { const env = loadEnv(mode, process.cwd()) const qBittorrentPort = env.VITE_QBITTORRENT_PORT ?? '8080' const proxyTarget = env.VITE_QBITTORRENT_TARGET ?? 'http://127.0.0.1' - const version = process.env.NODE_ENV === 'production' ? process.env.npm_package_version : JSON.stringify(process.env.npm_package_version) + const version = command === 'build' ? process.env.npm_package_version : JSON.stringify(process.env.npm_package_version) return { base: './', @@ -22,7 +22,12 @@ export default defineConfig(({ mode }) => { rollupOptions: { output: { manualChunks: { - vue: ['vue', 'vue-router'] + // vue stuff + vue: ['vue', 'vue-router', 'pinia', 'pinia-plugin-persist'], + // vuetify stuff + vuetify: ['vuetify'], + // faker stuff + faker: ['@faker-js/faker'] } } }