From 8930e139b28ff165cca2d818199505a727106466 Mon Sep 17 00:00:00 2001 From: cgoIT Date: Sat, 8 Apr 2023 19:33:33 +0200 Subject: [PATCH 01/17] fix(roman-numeral-converter): input validation and feedback (#332) * fix(roman-numeral-converter): checks for valid input and conversion enhancements Validates if numeral values are between 1 and 3999999. Validates if a roman number is valid. * fix(roman-numeral-converter): optimize logic for copy button * fix(roman-numeral-converter): changes due to review --- .../roman-numeral-converter.service.test.ts | 18 ++++---- .../roman-numeral-converter.service.ts | 13 +++++- .../roman-numeral-converter.vue | 45 ++++++++++++++++--- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/tools/roman-numeral-converter/roman-numeral-converter.service.test.ts b/src/tools/roman-numeral-converter/roman-numeral-converter.service.test.ts index 21a747cf..5ec9dd47 100644 --- a/src/tools/roman-numeral-converter/roman-numeral-converter.service.test.ts +++ b/src/tools/roman-numeral-converter/roman-numeral-converter.service.test.ts @@ -13,14 +13,17 @@ describe('roman-numeral-converter', () => { expect(arabicToRoman(0.9)).toEqual(''); }); + it('should convert numbers greater than 3999 to empty string', () => { + expect(arabicToRoman(3999.1)).toEqual(''); + expect(arabicToRoman(4000)).toEqual(''); + expect(arabicToRoman(10000)).toEqual(''); + }); + it('should convert floating points number to the lower integer in roman version', () => { - expect(arabicToRoman(-100)).toEqual(''); - expect(arabicToRoman(-42)).toEqual(''); - expect(arabicToRoman(-26)).toEqual(''); - expect(arabicToRoman(-10)).toEqual(''); - expect(arabicToRoman(0)).toEqual(''); - expect(arabicToRoman(0.5)).toEqual(''); - expect(arabicToRoman(0.9)).toEqual(''); + expect(arabicToRoman(1.1)).toEqual('I'); + expect(arabicToRoman(1.9)).toEqual('I'); + expect(arabicToRoman(17.6)).toEqual('XVII'); + expect(arabicToRoman(29.999)).toEqual('XXIX'); }); it('should convert positive integers to roman numbers', () => { @@ -67,7 +70,6 @@ describe('roman-numeral-converter', () => { expect(arabicToRoman(999)).toEqual('CMXCIX'); expect(arabicToRoman(1000)).toEqual('M'); expect(arabicToRoman(2000)).toEqual('MM'); - expect(arabicToRoman(9000)).toEqual('MMMMMMMMM'); }); }); }); diff --git a/src/tools/roman-numeral-converter/roman-numeral-converter.service.ts b/src/tools/roman-numeral-converter/roman-numeral-converter.service.ts index df2408a0..98afec67 100644 --- a/src/tools/roman-numeral-converter/roman-numeral-converter.service.ts +++ b/src/tools/roman-numeral-converter/roman-numeral-converter.service.ts @@ -1,5 +1,7 @@ +export const MIN_ARABIC_TO_ROMAN = 1; +export const MAX_ARABIC_TO_ROMAN = 3999; export function arabicToRoman(num: number) { - if (num < 1) return ''; + if (num < MIN_ARABIC_TO_ROMAN || num > MAX_ARABIC_TO_ROMAN) return ''; const lookup: { [key: string]: number } = { M: 1000, @@ -26,7 +28,16 @@ export function arabicToRoman(num: number) { return roman; } +const ROMAN_NUMBER_REGEX = new RegExp(/^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/); + +export function isValidRomanNumber(romanNumber: string) { + return ROMAN_NUMBER_REGEX.test(romanNumber); +} + export function romanToArabic(s: string) { + if (!isValidRomanNumber(s)) { + return null; + } const map: { [key: string]: number } = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; return [...s].reduce((r, c, i, s) => (map[s[i + 1]] > map[c] ? r - map[c] : r + map[c]), 0); } diff --git a/src/tools/roman-numeral-converter/roman-numeral-converter.vue b/src/tools/roman-numeral-converter/roman-numeral-converter.vue index c55380d0..609b46c4 100644 --- a/src/tools/roman-numeral-converter/roman-numeral-converter.vue +++ b/src/tools/roman-numeral-converter/roman-numeral-converter.vue @@ -2,21 +2,29 @@
- + + +
{{ outputRoman }}
- Copy + + Copy +

- + + +
{{ outputNumeral }}
- Copy + + Copy +
@@ -25,14 +33,41 @@ From 28145e0ffeb1f1c97cc503fb1e82ec18e8ebd588 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Sat, 8 Apr 2023 21:10:00 +0200 Subject: [PATCH 02/17] feat(new-tool): ipv4 address converter --- src/tools/index.ts | 3 +- src/tools/ipv4-address-converter/index.ts | 12 ++++ .../ipv4-address-converter.service.test.ts | 36 +++++++++++ .../ipv4-address-converter.service.ts | 38 +++++++++++ .../ipv4-address-converter.vue | 64 +++++++++++++++++++ 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/tools/ipv4-address-converter/index.ts create mode 100644 src/tools/ipv4-address-converter/ipv4-address-converter.service.test.ts create mode 100644 src/tools/ipv4-address-converter/ipv4-address-converter.service.ts create mode 100644 src/tools/ipv4-address-converter/ipv4-address-converter.vue diff --git a/src/tools/index.ts b/src/tools/index.ts index 8ea8b54a..f1d63b79 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as ipv4AddressConverter } from './ipv4-address-converter'; import { tool as benchmarkBuilder } from './benchmark-builder'; import { tool as userAgentParser } from './user-agent-parser'; import { tool as ipv4SubnetCalculator } from './ipv4-subnet-calculator'; @@ -103,7 +104,7 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Network', - components: [ipv4SubnetCalculator, macAddressLookup], + components: [ipv4SubnetCalculator, ipv4AddressConverter, macAddressLookup], }, { name: 'Math', diff --git a/src/tools/ipv4-address-converter/index.ts b/src/tools/ipv4-address-converter/index.ts new file mode 100644 index 00000000..62d2daf1 --- /dev/null +++ b/src/tools/ipv4-address-converter/index.ts @@ -0,0 +1,12 @@ +import { Binary } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Ipv4 address converter', + path: '/ipv4-address-converter', + description: 'Convert an ip address into decimal, binary, hexadecimal or event in ipv6', + keywords: ['ipv4', 'address', 'converter', 'decimal', 'hexadecimal', 'binary', 'ipv6'], + component: () => import('./ipv4-address-converter.vue'), + icon: Binary, + createdAt: new Date('2023-04-08'), +}); diff --git a/src/tools/ipv4-address-converter/ipv4-address-converter.service.test.ts b/src/tools/ipv4-address-converter/ipv4-address-converter.service.test.ts new file mode 100644 index 00000000..ecdcfa2c --- /dev/null +++ b/src/tools/ipv4-address-converter/ipv4-address-converter.service.test.ts @@ -0,0 +1,36 @@ +import { expect, describe, it } from 'vitest'; +import { isValidIpv4, ipv4ToInt } from './ipv4-address-converter.service'; + +describe('ipv4-address-converter', () => { + describe('ipv4ToInt', () => { + it('should convert an IPv4 address to an integer', () => { + expect(ipv4ToInt({ ip: '192.168.0.1' })).toBe(3232235521); + expect(ipv4ToInt({ ip: '10.0.0.1' })).toBe(167772161); + expect(ipv4ToInt({ ip: '255.255.255.255' })).toBe(4294967295); + }); + }); + + describe('isValidIpv4', () => { + it('should return true for a valid IP address', () => { + expect(isValidIpv4({ ip: '192.168.0.1' })).to.equal(true); + expect(isValidIpv4({ ip: '10.0.0.1' })).to.equal(true); + }); + + it('should return false for an invalid IP address', () => { + expect(isValidIpv4({ ip: '256.168.0.1' })).to.equal(false); + expect(isValidIpv4({ ip: '192.168.0' })).to.equal(false); + expect(isValidIpv4({ ip: '192.168.0.1.2' })).to.equal(false); + expect(isValidIpv4({ ip: '192.168.0.1.' })).to.equal(false); + expect(isValidIpv4({ ip: '.192.168.0.1' })).to.equal(false); + expect(isValidIpv4({ ip: '192.168.0.a' })).to.equal(false); + }); + + it('should return false for crap as input', () => { + expect(isValidIpv4({ ip: '' })).to.equal(false); + expect(isValidIpv4({ ip: ' ' })).to.equal(false); + expect(isValidIpv4({ ip: 'foo' })).to.equal(false); + expect(isValidIpv4({ ip: '-1' })).to.equal(false); + expect(isValidIpv4({ ip: '0' })).to.equal(false); + }); + }); +}); diff --git a/src/tools/ipv4-address-converter/ipv4-address-converter.service.ts b/src/tools/ipv4-address-converter/ipv4-address-converter.service.ts new file mode 100644 index 00000000..ffd5d804 --- /dev/null +++ b/src/tools/ipv4-address-converter/ipv4-address-converter.service.ts @@ -0,0 +1,38 @@ +import _ from 'lodash'; + +export { ipv4ToInt, ipv4ToIpv6, isValidIpv4 }; + +function ipv4ToInt({ ip }: { ip: string }) { + if (!isValidIpv4({ ip })) { + return 0; + } + + return ip + .trim() + .split('.') + .reduce((acc, part, index) => acc + Number(part) * Math.pow(256, 3 - index), 0); +} + +function ipv4ToIpv6({ ip, prefix = '0000:0000:0000:0000:0000:ffff:' }: { ip: string; prefix?: string }) { + if (!isValidIpv4({ ip })) { + return ''; + } + + return ( + prefix + + _.chain(ip) + .trim() + .split('.') + .map((part) => parseInt(part).toString(16).padStart(2, '0')) + .chunk(2) + .map((blocks) => blocks.join('')) + .join(':') + .value() + ); +} + +function isValidIpv4({ ip }: { ip: string }) { + const cleanIp = ip.trim(); + + return /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/.test(cleanIp); +} diff --git a/src/tools/ipv4-address-converter/ipv4-address-converter.vue b/src/tools/ipv4-address-converter/ipv4-address-converter.vue new file mode 100644 index 00000000..2721b1a8 --- /dev/null +++ b/src/tools/ipv4-address-converter/ipv4-address-converter.vue @@ -0,0 +1,64 @@ + + + + + From d7a503b4ae6363c419dd7c47e97aa5b3e865a074 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Sat, 8 Apr 2023 21:10:00 +0200 Subject: [PATCH 03/17] feat(new-tool): ipv4 address converter --- components.d.ts | 1 + src/tools/ipv4-address-converter/ipv4-address-converter.vue | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components.d.ts b/components.d.ts index 8fa004ea..bc2489ff 100644 --- a/components.d.ts +++ b/components.d.ts @@ -25,6 +25,7 @@ declare module '@vue/runtime-core' { NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NColorPicker: typeof import('naive-ui')['NColorPicker'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] + NCopyableInput: typeof import('naive-ui')['NCopyableInput'] NDatePicker: typeof import('naive-ui')['NDatePicker'] NDivider: typeof import('naive-ui')['NDivider'] NDynamicInput: typeof import('naive-ui')['NDynamicInput'] diff --git a/src/tools/ipv4-address-converter/ipv4-address-converter.vue b/src/tools/ipv4-address-converter/ipv4-address-converter.vue index 2721b1a8..7474608e 100644 --- a/src/tools/ipv4-address-converter/ipv4-address-converter.vue +++ b/src/tools/ipv4-address-converter/ipv4-address-converter.vue @@ -26,7 +26,7 @@ import { useValidation } from '@/composable/validation'; import { convertBase } from '../integer-base-converter/integer-base-converter.model'; import { ipv4ToInt, ipv4ToIpv6, isValidIpv4 } from './ipv4-address-converter.service'; -const rawIpAddress = ref('192.168.1.1'); +const rawIpAddress = useStorage('ipv4-converter:ip', '192.168.1.1'); const convertedSections = computed(() => { const ipInDecimal = ipv4ToInt({ ip: rawIpAddress.value }); From d2a26867054d883ddb883fbacdf2a5bf76b3149c Mon Sep 17 00:00:00 2001 From: cgoIT Date: Sun, 9 Apr 2023 14:03:17 +0200 Subject: [PATCH 04/17] fix(mac-address-lookup): fix typo in mac-address-lookup tool (#345) --- src/tools/mac-address-lookup/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/mac-address-lookup/index.ts b/src/tools/mac-address-lookup/index.ts index 574ee3f8..4108bc33 100644 --- a/src/tools/mac-address-lookup/index.ts +++ b/src/tools/mac-address-lookup/index.ts @@ -2,10 +2,10 @@ import { Devices } from '@vicons/tabler'; import { defineTool } from '../tool'; export const tool = defineTool({ - name: 'MAC adrdress lookup', - path: '/mac-adrdress-lookup', + name: 'MAC address lookup', + path: '/mac-address-lookup', description: 'Find the vendor and manufacturer of a device by its MAC address.', - keywords: ['mac', 'adrdress', 'lookup', 'vendor', 'parser', 'manufacturer'], + keywords: ['mac', 'address', 'lookup', 'vendor', 'parser', 'manufacturer'], component: () => import('./mac-address-lookup.vue'), icon: Devices, createdAt: new Date('2023-04-06'), From ebfdb64fde5e3290321230e14e5889c4a139b928 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Sun, 9 Apr 2023 15:27:58 +0200 Subject: [PATCH 05/17] chore(cd): don't run nightly release if their is no new commits --- .github/workflows/docker-nightly-release.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/docker-nightly-release.yml b/.github/workflows/docker-nightly-release.yml index 8126d02c..c75d9281 100644 --- a/.github/workflows/docker-nightly-release.yml +++ b/.github/workflows/docker-nightly-release.yml @@ -6,8 +6,26 @@ on: - cron: '0 0 * * *' jobs: + check_date: + runs-on: ubuntu-latest + name: Check latest commit + outputs: + should_run: ${{ steps.should_run.outputs.should_run }} + steps: + - uses: actions/checkout@v2 + - name: print latest_commit + run: echo ${{ github.sha }} + + - id: should_run + continue-on-error: true + name: check latest commit is less than a day + if: ${{ github.event_name == 'schedule' }} + run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false" + ci: runs-on: ubuntu-latest + needs: check_date + if: ${{ needs.check_date.outputs.should_run != 'false' }} steps: - uses: actions/checkout@v3 From ec7cb9351cd6dbf6a50fb086f8b40fe87c51ea83 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Sun, 9 Apr 2023 17:59:57 +0200 Subject: [PATCH 06/17] feat(test): added e2e tests --- .github/workflows/playwright.yml | 23 ++++++ .gitignore | 5 +- components.d.ts | 1 - package.json | 2 + playwright.config.ts | 80 +++++++++++++++++++ pnpm-lock.yaml | 20 +++++ scripts/create-tool.mjs | 23 ++++++ .../otp-code-generator.e2e.spec.ts | 48 +++++++++++ .../token-display.vue | 16 +++- .../token-generator.e2e.spec.ts | 19 +++++ vitest.config.ts | 13 +++ 11 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/playwright.yml create mode 100644 playwright.config.ts create mode 100644 src/tools/otp-code-generator-and-validator/otp-code-generator.e2e.spec.ts create mode 100644 src/tools/token-generator/token-generator.e2e.spec.ts create mode 100644 vitest.config.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 00000000..13d09495 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,23 @@ +name: E2E tests +on: + pull_request: + push: + branches: + - main +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: corepack enable + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'pnpm' + - name: Install dependencies + run: pnpm install + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Run Playwright tests + run: pnpm exec playwright test diff --git a/.gitignore b/.gitignore index cd1e2011..2cfe718a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,7 @@ coverage *.sln *.sw? -.env \ No newline at end of file +.env +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/components.d.ts b/components.d.ts index bc2489ff..8fa004ea 100644 --- a/components.d.ts +++ b/components.d.ts @@ -25,7 +25,6 @@ declare module '@vue/runtime-core' { NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NColorPicker: typeof import('naive-ui')['NColorPicker'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] - NCopyableInput: typeof import('naive-ui')['NCopyableInput'] NDatePicker: typeof import('naive-ui')['NDatePicker'] NDivider: typeof import('naive-ui')['NDivider'] NDynamicInput: typeof import('naive-ui')['NDynamicInput'] diff --git a/package.json b/package.json index bcbb3a07..e174aa35 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "preview": "vite preview --port 5050", "test": "npm run test:unit", "test:unit": "vitest --environment jsdom", + "test:e2e": "playwright test", "coverage": "vitest run --coverage", "typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore", @@ -75,6 +76,7 @@ "vue-router": "^4.1.6" }, "devDependencies": { + "@playwright/test": "^1.32.2", "@rushstack/eslint-patch": "^1.2.0", "@types/bcryptjs": "^2.4.2", "@types/crypto-js": "^4.1.1", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..cb6d3072 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,80 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './src', + testMatch: /.*\.e2e\.(spec\.)?ts/, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + testIdAttribute: 'data-test-id', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev', + url: 'http://127.0.0.1:3000', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f07788b1..bbd815d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,6 +126,9 @@ dependencies: version: 4.1.6(vue@3.2.47) devDependencies: + '@playwright/test': + specifier: ^1.32.2 + version: 1.32.2 '@rushstack/eslint-patch': specifier: ^1.2.0 version: 1.2.0 @@ -1721,6 +1724,17 @@ packages: tslib: 2.5.0 dev: true + /@playwright/test@1.32.2: + resolution: {integrity: sha512-nhaTSDpEdTTttdkDE8Z6K3icuG1DVRxrl98Qq0Lfc63SS9a2sjc9+x8ezysh7MzCKz6Y+nArml3/mmt+gqRmQQ==} + engines: {node: '>=14'} + hasBin: true + dependencies: + '@types/node': 16.18.22 + playwright-core: 1.32.2 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /@polka/url@1.0.0-next.21: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true @@ -6800,6 +6814,12 @@ packages: engines: {node: '>=10'} dev: false + /playwright-core@1.32.2: + resolution: {integrity: sha512-zD7aonO+07kOTthsrCR3YCVnDcqSHIJpdFUtZEMOb6//1Rc7/6mZDRdw+nlzcQiQltOOsiqI3rrSyn/SlyjnJQ==} + engines: {node: '>=14'} + hasBin: true + dev: true + /pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} diff --git a/scripts/create-tool.mjs b/scripts/create-tool.mjs index 36a20d8e..a6e16f33 100644 --- a/scripts/create-tool.mjs +++ b/scripts/create-tool.mjs @@ -5,6 +5,7 @@ import { fileURLToPath } from 'url'; const currentDirname = dirname(fileURLToPath(import.meta.url)); const toolsDir = join(currentDirname, '..', 'src', 'tools'); +// eslint-disable-next-line no-undef const toolName = process.argv[2]; if (!toolName) { @@ -73,6 +74,28 @@ import { expect, describe, it } from 'vitest'; `, ); +createToolFile( + `${toolName}.e2e.spec.ts`, + ` +import { test, expect } from '@playwright/test'; + +test.describe('Tool - ${toolNameTitleCase}', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/${toolName}'); + }); + + test('Has correct title', async ({ page }) => { + await expect(page).toHaveTitle('${toolNameTitleCase} - IT Tools'); + }); + + test('', async ({ page }) => { + + }); +}); + +`, +); + const toolsIndex = join(toolsDir, 'index.ts'); const indexContent = await readFile(toolsIndex, { encoding: 'utf-8' }).then((r) => r.split('\n')); diff --git a/src/tools/otp-code-generator-and-validator/otp-code-generator.e2e.spec.ts b/src/tools/otp-code-generator-and-validator/otp-code-generator.e2e.spec.ts new file mode 100644 index 00000000..6188f82f --- /dev/null +++ b/src/tools/otp-code-generator-and-validator/otp-code-generator.e2e.spec.ts @@ -0,0 +1,48 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Tool - OTP code generator', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/otp-generator'); + }); + + test('Has title', async ({ page }) => { + await expect(page).toHaveTitle('OTP code generator - IT Tools'); + }); + + test('Secret hexa value is computed from provided secret', async ({ page }) => { + await page.getByPlaceholder('Paste your TOTP secret...').fill('ITTOOLS'); + + const secretInHex = await page.getByPlaceholder('Secret in hex will be displayed here').inputValue(); + + expect(secretInHex).toEqual('44e6e72e02'); + }); + + test('OTP a generated from the provided secret', async ({ page }) => { + page.evaluate(() => { + Date.now = () => 1609477200000; //Jan 1, 2021 + }); + + await page.getByPlaceholder('Paste your TOTP secret...').fill('ITTOOLS'); + + const previousOtp = await page.getByTestId('previous-otp').innerText(); + const currentOtp = await page.getByTestId('current-otp').innerText(); + const nextOtp = await page.getByTestId('next-otp').innerText(); + + expect(previousOtp.trim()).toEqual('028034'); + expect(currentOtp.trim()).toEqual('162195'); + expect(nextOtp.trim()).toEqual('452815'); + }); + + test('You can generate a new random secret', async ({ page }) => { + const initialSecret = await page.getByPlaceholder('Paste your TOTP secret...').inputValue(); + await page + .locator('div') + .filter({ hasText: /^Secret$/ }) + .getByRole('button') + .click(); + + const newSecret = await page.getByPlaceholder('Paste your TOTP secret...').inputValue(); + + expect(newSecret).not.toEqual(initialSecret); + }); +}); diff --git a/src/tools/otp-code-generator-and-validator/token-display.vue b/src/tools/otp-code-generator-and-validator/token-display.vue index 6ead65c8..ce11ccd5 100644 --- a/src/tools/otp-code-generator-and-validator/token-display.vue +++ b/src/tools/otp-code-generator-and-validator/token-display.vue @@ -8,13 +8,21 @@
{{ previousCopied ? 'Copied !' : 'Copy previous OTP' }}
@@ -22,7 +30,9 @@
{{ nextCopied ? 'Copied !' : 'Copy next OTP' }}
diff --git a/src/tools/token-generator/token-generator.e2e.spec.ts b/src/tools/token-generator/token-generator.e2e.spec.ts new file mode 100644 index 00000000..905a81cc --- /dev/null +++ b/src/tools/token-generator/token-generator.e2e.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Tool - Token generator', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/token-generator'); + }); + + test('Has title', async ({ page }) => { + await expect(page).toHaveTitle('Token generator - IT Tools'); + }); + + test('New token on refresh', async ({ page }) => { + const initialToken = await page.getByPlaceholder('The token...').inputValue(); + await page.getByRole('button', { name: 'Refresh' }).click(); + const newToken = await page.getByPlaceholder('The token...').inputValue(); + + expect(newToken).not.toEqual(initialToken); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..1c0d1e52 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { configDefaults, defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + test: { + exclude: [...configDefaults.exclude, '**/*.e2e.spec.ts'], + }, +}); From 1d7f8b9a8c2dcdc0cc08b238f12cebb3941121ed Mon Sep 17 00:00:00 2001 From: cgoIT Date: Sun, 9 Apr 2023 20:58:27 +0200 Subject: [PATCH 07/17] feat(new-tool): generate ula based on timestamp and mac address (#344) * feat(ipv6-ula-generator): new tool: generate ula based on timestamp and mac address This new tool generates a random unique ula based on the current timestamp and the provided mac address. An ULA is your "secondary" IPV6-Address only for internal use. It can also be used as a backup address if your provider gets offline and your Prefix-IPs are not longer valid.Also you can create the most of your internal firewall rules based on your ULAs. * feat(ipv6-ula-generator): changes requested by review * Update src/tools/ipv6-ula-generator/index.ts * Update src/tools/ipv6-ula-generator/ipv6-ula-generator.vue --------- Co-authored-by: Corentin THOMASSET --- src/composable/validation.ts | 2 +- src/tools/index.ts | 3 +- src/tools/ipv6-ula-generator/index.ts | 13 ++++ .../ipv6-ula-generator/ipv6-ula-generator.vue | 64 +++++++++++++++++++ .../mac-address-lookup/mac-address-lookup.vue | 12 +--- src/utils/macAddress.ts | 16 +++++ 6 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 src/tools/ipv6-ula-generator/index.ts create mode 100644 src/tools/ipv6-ula-generator/ipv6-ula-generator.vue create mode 100644 src/utils/macAddress.ts diff --git a/src/composable/validation.ts b/src/composable/validation.ts index fc008de8..2c58b60b 100644 --- a/src/composable/validation.ts +++ b/src/composable/validation.ts @@ -20,7 +20,7 @@ export function isFalsyOrHasThrown(cb: () => ValidatorReturnType): boolean { } } -type ValidationAttrs = { +export type ValidationAttrs = { feedback: string; validationStatus: string | undefined; }; diff --git a/src/tools/index.ts b/src/tools/index.ts index f1d63b79..c68c20aa 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as ipv6UlaGenerator } from './ipv6-ula-generator'; import { tool as ipv4AddressConverter } from './ipv4-address-converter'; import { tool as benchmarkBuilder } from './benchmark-builder'; import { tool as userAgentParser } from './user-agent-parser'; @@ -104,7 +105,7 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Network', - components: [ipv4SubnetCalculator, ipv4AddressConverter, macAddressLookup], + components: [ipv4SubnetCalculator, ipv4AddressConverter, macAddressLookup, ipv6UlaGenerator], }, { name: 'Math', diff --git a/src/tools/ipv6-ula-generator/index.ts b/src/tools/ipv6-ula-generator/index.ts new file mode 100644 index 00000000..def3dfed --- /dev/null +++ b/src/tools/ipv6-ula-generator/index.ts @@ -0,0 +1,13 @@ +import { BuildingFactory } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'IPv6 ULA generator', + path: '/ipv6-ula-generator', + description: + 'Generate your own local, non-routable IP addresses on your network according to RFC4193.', + keywords: ['ipv6', 'ula', 'generator', 'rfc4193', 'network', 'private'], + component: () => import('./ipv6-ula-generator.vue'), + icon: BuildingFactory, + createdAt: new Date('2023-04-09'), +}); diff --git a/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue b/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue new file mode 100644 index 00000000..d5c3766d --- /dev/null +++ b/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/tools/mac-address-lookup/mac-address-lookup.vue b/src/tools/mac-address-lookup/mac-address-lookup.vue index 60e4836b..99b8342d 100644 --- a/src/tools/mac-address-lookup/mac-address-lookup.vue +++ b/src/tools/mac-address-lookup/mac-address-lookup.vue @@ -30,23 +30,15 @@ diff --git a/src/utils/macAddress.ts b/src/utils/macAddress.ts new file mode 100644 index 00000000..5451a36c --- /dev/null +++ b/src/utils/macAddress.ts @@ -0,0 +1,16 @@ +import { useValidation, type ValidationAttrs } from '@/composable/validation'; +import type { Ref } from 'vue'; + +function macAddressValidation(value: Ref) { + return useValidation({ + source: value, + rules: [ + { + message: 'Invalid MAC address', + validator: (value) => value.trim().match(/^([0-9A-Fa-f]{2}[:-]){2,5}([0-9A-Fa-f]{2})$/), + }, + ], + }); +} + +export { macAddressValidation }; From a1e983538c5aad1bb7e361873a2d6af1d349ceae Mon Sep 17 00:00:00 2001 From: cgoIT Date: Sun, 9 Apr 2023 20:58:27 +0200 Subject: [PATCH 08/17] feat(new-tool): generate ula based on timestamp and mac address (#344) * feat(ipv6-ula-generator): new tool: generate ula based on timestamp and mac address This new tool generates a random unique ula based on the current timestamp and the provided mac address. An ULA is your "secondary" IPV6-Address only for internal use. It can also be used as a backup address if your provider gets offline and your Prefix-IPs are not longer valid.Also you can create the most of your internal firewall rules based on your ULAs. * feat(ipv6-ula-generator): changes requested by review * Update src/tools/ipv6-ula-generator/index.ts * Update src/tools/ipv6-ula-generator/ipv6-ula-generator.vue --------- Co-authored-by: Corentin THOMASSET --- src/tools/ipv6-ula-generator/ipv6-ula-generator.vue | 9 +++++---- src/utils/macAddress.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue b/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue index d5c3766d..ee74d4cc 100644 --- a/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue +++ b/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue @@ -2,7 +2,7 @@
- This page uses the first method suggested by IETF using the current timestamp plus the mac address, sha1 hashed, + This tool uses the first method suggested by IETF using the current timestamp plus the mac address, sha1 hashed, and the lower 40 bits to generate your random ULA. @@ -40,19 +40,20 @@ const calculatedSections = computed(() => { const hex40bit = SHA1(timestamp + macAddress.value) .toString() .substring(30); + const ula = 'fd' + hex40bit.substring(0, 2) + ':' + hex40bit.substring(2, 6) + ':' + hex40bit.substring(6); return [ { - label: 'IPv6 ULA : ', + label: 'IPv6 ULA:', value: `${ula}::/48`, }, { - label: 'First routable block: ', + label: 'First routable block:', value: `${ula}:0::/64`, }, { - label: 'Last routable block: ', + label: 'Last routable block:', value: `${ula}:ffff::/64`, }, ]; diff --git a/src/utils/macAddress.ts b/src/utils/macAddress.ts index 5451a36c..ff6000cb 100644 --- a/src/utils/macAddress.ts +++ b/src/utils/macAddress.ts @@ -1,4 +1,4 @@ -import { useValidation, type ValidationAttrs } from '@/composable/validation'; +import { useValidation } from '@/composable/validation'; import type { Ref } from 'vue'; function macAddressValidation(value: Ref) { From 9d639edf2dde311989b2b5470594cf776ee88f0d Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Mon, 10 Apr 2023 13:00:38 +0200 Subject: [PATCH 09/17] refactor(lint): auto fix lint --- src/components/TextareaCopyable.vue | 2 +- src/layouts/base.layout.vue | 10 ++++------ src/tools/ipv6-ula-generator/index.ts | 3 +-- .../lorem-ipsum-generator/lorem-ipsum-generator.vue | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue index 0668ef28..9bcb65cc 100644 --- a/src/components/TextareaCopyable.vue +++ b/src/components/TextareaCopyable.vue @@ -13,7 +13,7 @@