From d70b65029dfbbc6c0a35b8694e544bde1c748e61 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Tue, 10 Dec 2024 20:34:48 +0000 Subject: [PATCH] [TEST] Add tests for copyRecursiveAsync function and update Jest configuration --- jest.config.js | 3 +- jest.setup.ts | 1 + package.json | 6 +- src/backend/__tests__/utils.test.ts | 103 +++++++++++++++++++++++++++- yarn.lock | 64 +++++++++++++++-- 5 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 jest.setup.ts diff --git a/jest.config.js b/jest.config.js index 005f6f994..eb5cba75b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,7 +5,8 @@ module.exports = { globals: { 'ts-jest': {} }, - + testTimeout: 35000, + setupFilesAfterEnv: ['./jest.setup.ts'], collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/*.config.js'], coverageDirectory: '/coverage', coveragePathIgnorePatterns: [ diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 000000000..31775ac4d --- /dev/null +++ b/jest.setup.ts @@ -0,0 +1 @@ +jest.setTimeout(35000) diff --git a/package.json b/package.json index 3c7eb4f79..f9e35086d 100644 --- a/package.json +++ b/package.json @@ -324,7 +324,7 @@ "@types/express": "^4.17.21", "@types/i18next-fs-backend": "^1.1.5", "@types/ini": "^1.3.34", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.14", "@types/jsdom": "^20.0.1", "@types/mime": "^3.0.2", "@types/node": "^20.16.2", @@ -334,6 +334,7 @@ "@types/react-blockies": "^1.4.1", "@types/react-dom": "^18.0.8", "@types/react-router-dom": "^5.3.3", + "@types/rimraf": "^4.0.5", "@types/tmp": "^0.2.6", "@types/ws": "^8.5.12", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -357,9 +358,10 @@ "prettier": "^2.8.8", "pretty-quick": "^4.0.0", "react-markdown": "^9.0.1", + "rimraf": "^6.0.1", "sass": "^1.55.0", "tmp": "^0.2.3", - "ts-jest": "^29.1.1", + "ts-jest": "^29.2.5", "type-fest": "^3.2.0", "typescript": "5.3.3", "vite": "^5.4.0", diff --git a/src/backend/__tests__/utils.test.ts b/src/backend/__tests__/utils.test.ts index 93059febc..d3007ba8a 100644 --- a/src/backend/__tests__/utils.test.ts +++ b/src/backend/__tests__/utils.test.ts @@ -1,5 +1,11 @@ import * as utils from '../utils' -import { getExecutableAndArgs } from '../utils' +import { getExecutableAndArgs, copyRecursiveAsync } from '../utils' +import { mkdir, writeFile, symlink } from 'fs/promises' +import { join } from 'path' +import { chmod, existsSync } from 'fs' +import { rimraf } from 'rimraf' +import os from 'os' +import * as fs from 'fs' jest.mock('electron') jest.mock('../logger/logger') @@ -261,4 +267,99 @@ describe('backend/utils.ts', () => { expect(getExecutableAndArgs(input)).toEqual(expected) }) }) + + describe.only('copyRecursiveAsync', () => { + const testDir = join(os.tmpdir(), `test-copy-${Date.now()}`) + const sourceDir = join(testDir, 'source') + const destDir = join(testDir, 'dest') + + beforeEach(async () => { + // Setup test directories + await mkdir(sourceDir, { recursive: true }) + await mkdir(destDir, { recursive: true }) + }) + + afterEach(async () => { + // Cleanup test directories + await rimraf(testDir) + }) + + it('should copy a single file', async () => { + const testFile = join(sourceDir, 'test.txt') + const destFile = join(destDir, 'test.txt') + await writeFile(testFile, 'test content') + + await copyRecursiveAsync(testFile, destFile) + + expect(existsSync(destFile)).toBe(true) + }) + + it('should copy a directory recursively', async () => { + const subDir = join(sourceDir, 'subdir') + const testFile = join(subDir, 'test.txt') + await mkdir(subDir, { recursive: true }) + await writeFile(testFile, 'test content') + + await copyRecursiveAsync(sourceDir, join(destDir, 'source')) + + expect(existsSync(join(destDir, 'source/subdir/test.txt'))).toBe(true) + }) + + it('should skip symbolic links', async () => { + const testFile = join(sourceDir, 'test.txt') + const linkFile = join(sourceDir, 'link.txt') + await writeFile(testFile, 'test content') + await symlink(testFile, linkFile) + + await copyRecursiveAsync(linkFile, join(destDir, 'link.txt')) + + expect(existsSync(join(destDir, 'link.txt'))).toBe(false) + }) + + it('should handle invalid source path', async () => { + const result = await copyRecursiveAsync('nonexistent', destDir) + expect(result).toBeUndefined() + }) + + it('should throw on permission error', async () => { + const testFile = join(sourceDir, 'test.txt') + await writeFile(testFile, 'test content') + + // Make destination directory read-only + await new Promise((resolve) => chmod(destDir, '444', resolve)) + + await expect( + copyRecursiveAsync(testFile, join(destDir, 'test.txt')) + ).rejects.toThrow('Permission error') + }) + + it('should throw on timeout', async () => { + const COPY_TIMEOUT_MS = 30000 + const testFile = join(sourceDir, 'test.txt') + await writeFile(testFile, 'test content') + + // Mock the copyFile function to simulate a slow operation + const mockCopyFile = jest + .spyOn(fs.promises, 'copyFile') + .mockImplementation(async () => { + return new Promise((resolve) => { + setTimeout(resolve, COPY_TIMEOUT_MS + 1000) // Wait longer than timeout + }) + }) + + const destFile = join(destDir, 'test.txt') + + await expect(copyRecursiveAsync(testFile, destFile)).rejects.toThrow( + 'Timeout' + ) + + // Restore original implementation + mockCopyFile.mockRestore() + }) + + it('should handle empty source and destination', async () => { + const result = await copyRecursiveAsync('', '') + expect(result).toBeUndefined() + }) + }) }) diff --git a/yarn.lock b/yarn.lock index 520cf3ce5..6982f5ec2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3435,10 +3435,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.12": - version "29.5.12" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" - integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== +"@types/jest@^29.5.14": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -3608,6 +3608,13 @@ dependencies: "@types/node" "*" +"@types/rimraf@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-4.0.5.tgz#7a59be11605c22ea3959c21ff8b28b9df1bae1b2" + integrity sha512-DTCZoIQotB2SUJnYgrEx43cQIUYOlNZz0AZPbKU4PSLYTUdML5Gox0++z4F9kQocxStrCmRNhi4x5x/UlwtKUA== + dependencies: + rimraf "*" + "@types/secp256k1@^4.0.6": version "4.0.6" resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" @@ -7897,6 +7904,18 @@ glob@^10.2.2, glob@^10.3.10: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +glob@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e" + integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^4.0.1" + minimatch "^10.0.0" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^2.0.0" + glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -8966,6 +8985,13 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jackspeak@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015" + integrity sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw== + dependencies: + "@isaacs/cliui" "^8.0.2" + jake@^10.8.5: version "10.9.2" resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" @@ -9888,6 +9914,11 @@ lru-cache@^10.0.1, lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +lru-cache@^11.0.0: + version "11.0.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.2.tgz#fbd8e7cf8211f5e7e5d91905c415a3f55755ca39" + integrity sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -10400,6 +10431,13 @@ minimatch@9.0.3: dependencies: brace-expansion "^2.0.1" +minimatch@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" + integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ== + dependencies: + brace-expansion "^2.0.1" + minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -11193,6 +11231,14 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-scurry@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" + integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + path-to-regexp@0.1.10: version "0.1.10" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" @@ -12131,6 +12177,14 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@*, rimraf@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.0.1.tgz#ffb8ad8844dd60332ab15f52bc104bc3ed71ea4e" + integrity sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A== + dependencies: + glob "^11.0.0" + package-json-from-dist "^1.0.0" + rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -13272,7 +13326,7 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== -ts-jest@^29.1.1: +ts-jest@^29.2.5: version "29.2.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==