diff --git a/frontend/package.json b/frontend/package.json index 7b25be0..04a8b65 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,10 +1,11 @@ { "name": "digidecs", "type": "module", - "version": "0.1.0", + "version": "0.1.1", "private": true, "scripts": { "dev": "vite", + "test": "vitest --config './src/tests/vitest.config.ts'", "build": "vue-tsc --noEmit && vite build", "preview": "vite preview", "lint": "eslint . --fix --ignore-path .gitignore" @@ -14,7 +15,7 @@ "base64-arraybuffer": "^1.0.2", "core-js": "^3.29.0", "roboto-fontface": "*", - "vue": "^3.2.0", + "vue": "^3.5.13", "vue-i18n": "9", "vue-router": "^4.0.0", "vuetify": "^3.0.0" @@ -26,7 +27,7 @@ "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", "@vitejs/plugin-vue": "^4.0.0", - "@vue/eslint-config-typescript": "^11.0.0", + "@vue/test-utils": "^2.4.6", "eslint": "^9.9.1", "eslint-plugin-vue": "^9.28.0", "sass": "^1.60.0", @@ -35,6 +36,8 @@ "unplugin-fonts": "^1.0.3", "vite": "^4.2.0", "vite-plugin-vuetify": "^1.0.0", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.0.5", "vue-tsc": "^2.1.8" } } diff --git a/frontend/src/scripts/iban.ts b/frontend/src/scripts/iban.ts new file mode 100644 index 0000000..b8026ca --- /dev/null +++ b/frontend/src/scripts/iban.ts @@ -0,0 +1,26 @@ +export abstract class IBAN{ + + static checkIBAN(iban: string): boolean { + // Tested with: + // GB33BUKB20201555555555 (correct) + // GB94BARC10201530093459 (correct) + // gB94BARC10201530093459 (correct, evaluates to its uppercase equivalent) + // GB94BARC20201530093459 (incorrect checksum) + iban = iban.toUpperCase(); + const ibanRegex = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/; + if (!ibanRegex.test(iban)) { + return false; + } + + // ISO/IEC 7064:2003 + // Convert IBAN to numeric representation (rearrange and replace letter with corresponding numeric value) + const numericIban = (iban.slice(4) + iban.slice(0,4)) + .replace(/[A-Z]/g, char => (parseInt(char, 36)).toString()); + // Match only sequences of 1-7 digits, convert each to number and add, then take result mod 97. + const remainder = numericIban + .match(/\d{1,7}/g)?.reduce((acc, block) => Number(acc + block) % 97, 0); + return remainder===1; + } + + +} \ No newline at end of file diff --git a/frontend/src/tests/IBAN.test.js b/frontend/src/tests/IBAN.test.js new file mode 100644 index 0000000..e5d0c5e --- /dev/null +++ b/frontend/src/tests/IBAN.test.js @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { mount } from '@vue/test-utils'; +import { checkIBAN } from '../views/HomeView.vue'; +import {IBAN} from "../scripts/iban"; + + +describe('checkIBAN', () => { + it('should return true for a valid IBAN', () => { + const validIBAN = 'DE89370400440532013000'; + expect(IBAN.checkIBAN(validIBAN)).toBe(true); + }); + + it('should return false for an invalid IBAN', () => { + const invalidIBAN = 'DE89370400440532013001'; + expect(IBAN.checkIBAN(invalidIBAN)).toBe(false); + }); + + it('should return false for an IBAN with incorrect length', () => { + const shortIBAN = 'DE8937040044053201300'; + expect(IBAN.checkIBAN(shortIBAN)).toBe(false); + }); + + it('should return false for an IBAN with invalid characters', () => { + const invalidCharIBAN = 'DE89$70400440532013000'; + expect(IBAN.checkIBAN(invalidCharIBAN)).toBe(false); + }); + + it('should return false for an empty IBAN', () => { + const emptyIBAN = ''; + expect(IBAN.checkIBAN(emptyIBAN)).toBe(false); + }); +}); \ No newline at end of file diff --git a/frontend/src/tests/vitest.config.ts b/frontend/src/tests/vitest.config.ts new file mode 100644 index 0000000..dc83c21 --- /dev/null +++ b/frontend/src/tests/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vitest/config'; +import vue from "@vitejs/plugin-vue"; +import viteTsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + test: { + globals: true, + environment: 'jsdom', + coverage: { + reporter: ['text', 'json', 'html'], + }, + include: ["src/tests/*.test.js"], + }, + plugins: [vue(),viteTsconfigPaths()], +}); \ No newline at end of file diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index 70e55f0..b37dc0f 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -120,6 +120,7 @@ import {defineComponent} from "vue"; import {InputValidationRules} from "@/main"; import {Digidecs} from "@/scripts/digidecs"; import MaterialBanner from "@/views/components/MaterialBanner.vue"; +import {IBAN} from "@/scripts/iban"; interface Data { error: string | undefined, @@ -145,28 +146,6 @@ interface Data { } } -function checkIBAN(iban: string): boolean { - // Tested with: - // GB33BUKB20201555555555 (correct) - // GB94BARC10201530093459 (correct) - // gB94BARC10201530093459 (correct, evaluates to its uppercase equivalent) - // GB94BARC20201530093459 (incorrect checksum) - iban = iban.toUpperCase(); - const ibanRegex = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/; - if (!ibanRegex.test(iban)) { - return false; - } - - // ISO/IEC 7064:2003 - // Convert IBAN to numeric representation (rearrange and replace letter with corresponding numeric value) - const numericIban = (iban.slice(4) + iban.slice(0,4)) - .replace(/[A-Z]/g, char => (parseInt(char, 36)).toString()); - // Match only sequences of 1-7 digits, convert each to number and add, then take result mod 97. - const remainder = numericIban - .match(/\d{1,7}/g)?.reduce((acc, block) => Number(acc + block) % 97, 0); - return remainder===1; -} - export default defineComponent({ components: {MaterialBanner}, data(): Data { @@ -180,7 +159,7 @@ export default defineComponent({ ], iban: [ v => !!v || this.$t("home.form.rules.required"), - v => checkIBAN(v) || this.$t("home.form.rules.ibanInvalid") + v => IBAN.checkIBAN(v) || this.$t("home.form.rules.ibanInvalid") ], email: [ v => !!v || this.$t("home.form.rules.required"), diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 84265ad..3adb696 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -17,7 +17,8 @@ "@/*": [ "src/*" ] - } + }, + "allowJs": true, // Necessary for vitest to work }, "include": [ "src/**/*.ts",