mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
Merge 7e0ae3ea83 into 44ecf41ca6
This commit is contained in:
commit
27bf6efaa0
67
admin/inertia/__tests__/lib/classNames.test.ts
Normal file
67
admin/inertia/__tests__/lib/classNames.test.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import classNames from '~/lib/classNames'
|
||||
|
||||
describe('classNames', () => {
|
||||
it('deve juntar múltiplas strings com espaço', () => {
|
||||
// Cenário
|
||||
const classes = ['foo', 'bar', 'baz']
|
||||
|
||||
// Ação
|
||||
const resultado = classNames(...classes)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('foo bar baz')
|
||||
})
|
||||
|
||||
it('deve ignorar valores undefined', () => {
|
||||
// Cenário
|
||||
const classe1 = 'foo'
|
||||
const classe2 = undefined
|
||||
const classe3 = 'bar'
|
||||
|
||||
// Ação
|
||||
const resultado = classNames(classe1, classe2, classe3)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('foo bar')
|
||||
})
|
||||
|
||||
it('deve ignorar strings vazias', () => {
|
||||
// Cenário
|
||||
const classe1 = 'foo'
|
||||
const classe2 = ''
|
||||
const classe3 = 'bar'
|
||||
|
||||
// Ação
|
||||
const resultado = classNames(classe1, classe2, classe3)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('foo bar')
|
||||
})
|
||||
|
||||
it('deve retornar string vazia quando chamado sem argumentos', () => {
|
||||
// Cenário
|
||||
// (nenhum argumento)
|
||||
|
||||
// Ação
|
||||
const resultado = classNames()
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('')
|
||||
})
|
||||
|
||||
it('deve lidar com mix de strings e undefined', () => {
|
||||
// Cenário
|
||||
const classe1 = undefined
|
||||
const classe2 = 'active'
|
||||
const classe3 = undefined
|
||||
const classe4 = 'visible'
|
||||
const classe5 = undefined
|
||||
|
||||
// Ação
|
||||
const resultado = classNames(classe1, classe2, classe3, classe4, classe5)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('active visible')
|
||||
})
|
||||
})
|
||||
120
admin/inertia/__tests__/lib/useDiskDisplayData.test.ts
Normal file
120
admin/inertia/__tests__/lib/useDiskDisplayData.test.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { getAllDiskDisplayItems, getPrimaryDiskInfo } from '~/hooks/useDiskDisplayData'
|
||||
import type { NomadDiskInfo } from '../../../types/system'
|
||||
import type { Systeminformation } from 'systeminformation'
|
||||
|
||||
const mockDisks: NomadDiskInfo[] = [
|
||||
{
|
||||
name: 'sda',
|
||||
totalSize: 1000000000,
|
||||
totalUsed: 500000000,
|
||||
percentUsed: 50,
|
||||
model: 'Test Disk',
|
||||
vendor: '',
|
||||
rota: false,
|
||||
tran: 'sata',
|
||||
size: '1000000000',
|
||||
filesystems: [],
|
||||
},
|
||||
]
|
||||
|
||||
const mockFsSize: Systeminformation.FsSizeData[] = [
|
||||
{
|
||||
fs: '/dev/sda1',
|
||||
type: 'ext4',
|
||||
size: 500000000,
|
||||
used: 250000000,
|
||||
available: 250000000,
|
||||
use: 50,
|
||||
mount: '/',
|
||||
rw: true,
|
||||
},
|
||||
]
|
||||
|
||||
describe('getAllDiskDisplayItems', () => {
|
||||
it('deve retornar items formatados com discos válidos', () => {
|
||||
// Cenário
|
||||
const disks = mockDisks
|
||||
const fsSize = undefined
|
||||
|
||||
// Ação
|
||||
const resultado = getAllDiskDisplayItems(disks, fsSize)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toHaveLength(1)
|
||||
expect(resultado[0].label).toBe('sda')
|
||||
expect(resultado[0].value).toBe(50)
|
||||
expect(resultado[0].totalBytes).toBe(1000000000)
|
||||
expect(resultado[0].usedBytes).toBe(500000000)
|
||||
})
|
||||
|
||||
it('deve usar fallback para fsSize quando discos são undefined', () => {
|
||||
// Cenário
|
||||
const disks = undefined
|
||||
const fsSize = mockFsSize
|
||||
|
||||
// Ação
|
||||
const resultado = getAllDiskDisplayItems(disks, fsSize)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toHaveLength(1)
|
||||
expect(resultado[0].label).toBe('/dev/sda1')
|
||||
expect(resultado[0].value).toBe(50)
|
||||
expect(resultado[0].totalBytes).toBe(500000000)
|
||||
expect(resultado[0].usedBytes).toBe(250000000)
|
||||
})
|
||||
|
||||
it('deve retornar array vazio quando ambos são undefined', () => {
|
||||
// Cenário
|
||||
const disks = undefined
|
||||
const fsSize = undefined
|
||||
|
||||
// Ação
|
||||
const resultado = getAllDiskDisplayItems(disks, fsSize)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getPrimaryDiskInfo', () => {
|
||||
it('deve retornar totalSize e totalUsed do maior disco', () => {
|
||||
// Cenário
|
||||
const disks = mockDisks
|
||||
const fsSize = undefined
|
||||
|
||||
// Ação
|
||||
const resultado = getPrimaryDiskInfo(disks, fsSize)
|
||||
|
||||
// Validação
|
||||
expect(resultado).not.toBeNull()
|
||||
expect(resultado!.totalSize).toBe(1000000000)
|
||||
expect(resultado!.totalUsed).toBe(500000000)
|
||||
})
|
||||
|
||||
it('deve usar fallback para fsSize quando não há discos', () => {
|
||||
// Cenário
|
||||
const disks = undefined
|
||||
const fsSize = mockFsSize
|
||||
|
||||
// Ação
|
||||
const resultado = getPrimaryDiskInfo(disks, fsSize)
|
||||
|
||||
// Validação
|
||||
expect(resultado).not.toBeNull()
|
||||
expect(resultado!.totalSize).toBe(500000000)
|
||||
expect(resultado!.totalUsed).toBe(250000000)
|
||||
})
|
||||
|
||||
it('deve retornar null quando ambos são vazios', () => {
|
||||
// Cenário
|
||||
const disks: NomadDiskInfo[] = []
|
||||
const fsSize: Systeminformation.FsSizeData[] = []
|
||||
|
||||
// Ação
|
||||
const resultado = getPrimaryDiskInfo(disks, fsSize)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBeNull()
|
||||
})
|
||||
})
|
||||
186
admin/inertia/__tests__/lib/util.test.ts
Normal file
186
admin/inertia/__tests__/lib/util.test.ts
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { capitalizeFirstLetter, formatBytes, extractFileName, generateRandomString } from '~/lib/util'
|
||||
|
||||
describe('capitalizeFirstLetter', () => {
|
||||
it('deve capitalizar a primeira letra de uma string', () => {
|
||||
// Cenário
|
||||
const entrada = 'hello'
|
||||
|
||||
// Ação
|
||||
const resultado = capitalizeFirstLetter(entrada)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('Hello')
|
||||
})
|
||||
|
||||
it('deve retornar string vazia para entrada vazia', () => {
|
||||
// Cenário
|
||||
const entrada = ''
|
||||
|
||||
// Ação
|
||||
const resultado = capitalizeFirstLetter(entrada)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('')
|
||||
})
|
||||
|
||||
it('deve retornar string vazia para null', () => {
|
||||
// Cenário
|
||||
const entrada = null
|
||||
|
||||
// Ação
|
||||
const resultado = capitalizeFirstLetter(entrada)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('')
|
||||
})
|
||||
|
||||
it('deve retornar string vazia para undefined', () => {
|
||||
// Cenário
|
||||
const entrada = undefined
|
||||
|
||||
// Ação
|
||||
const resultado = capitalizeFirstLetter(entrada)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatBytes', () => {
|
||||
it('deve retornar "0 Bytes" para 0 bytes', () => {
|
||||
// Cenário
|
||||
const bytes = 0
|
||||
|
||||
// Ação
|
||||
const resultado = formatBytes(bytes)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('0 Bytes')
|
||||
})
|
||||
|
||||
it('deve formatar 1024 bytes como "1 KB"', () => {
|
||||
// Cenário
|
||||
const bytes = 1024
|
||||
|
||||
// Ação
|
||||
const resultado = formatBytes(bytes)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('1 KB')
|
||||
})
|
||||
|
||||
it('deve formatar 1048576 bytes como "1 MB"', () => {
|
||||
// Cenário
|
||||
const bytes = 1048576
|
||||
|
||||
// Ação
|
||||
const resultado = formatBytes(bytes)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('1 MB')
|
||||
})
|
||||
|
||||
it('deve formatar 1073741824 bytes como "1 GB"', () => {
|
||||
// Cenário
|
||||
const bytes = 1073741824
|
||||
|
||||
// Ação
|
||||
const resultado = formatBytes(bytes)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('1 GB')
|
||||
})
|
||||
|
||||
it('deve formatar 500 bytes como "500 Bytes"', () => {
|
||||
// Cenário
|
||||
const bytes = 500
|
||||
|
||||
// Ação
|
||||
const resultado = formatBytes(bytes)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('500 Bytes')
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractFileName', () => {
|
||||
it('deve extrair nome do arquivo de caminho Unix', () => {
|
||||
// Cenário
|
||||
const caminho = '/home/user/file.txt'
|
||||
|
||||
// Ação
|
||||
const resultado = extractFileName(caminho)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('file.txt')
|
||||
})
|
||||
|
||||
it('deve extrair nome do arquivo de caminho Windows', () => {
|
||||
// Cenário
|
||||
const caminho = 'C:\\Users\\file.txt'
|
||||
|
||||
// Ação
|
||||
const resultado = extractFileName(caminho)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('file.txt')
|
||||
})
|
||||
|
||||
it('deve retornar o próprio nome quando não há caminho', () => {
|
||||
// Cenário
|
||||
const caminho = 'file.txt'
|
||||
|
||||
// Ação
|
||||
const resultado = extractFileName(caminho)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('file.txt')
|
||||
})
|
||||
|
||||
it('deve retornar string vazia para entrada vazia', () => {
|
||||
// Cenário
|
||||
const caminho = ''
|
||||
|
||||
// Ação
|
||||
const resultado = extractFileName(caminho)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('generateRandomString', () => {
|
||||
it('deve gerar string com o tamanho especificado', () => {
|
||||
// Cenário
|
||||
const tamanho = 10
|
||||
|
||||
// Ação
|
||||
const resultado = generateRandomString(tamanho)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toHaveLength(10)
|
||||
})
|
||||
|
||||
it('deve retornar string vazia para tamanho 0', () => {
|
||||
// Cenário
|
||||
const tamanho = 0
|
||||
|
||||
// Ação
|
||||
const resultado = generateRandomString(tamanho)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('')
|
||||
})
|
||||
|
||||
it('deve conter apenas caracteres alfanuméricos', () => {
|
||||
// Cenário
|
||||
const tamanho = 100
|
||||
|
||||
// Ação
|
||||
const resultado = generateRandomString(tamanho)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toMatch(/^[A-Za-z0-9]+$/)
|
||||
})
|
||||
})
|
||||
1113
admin/package-lock.json
generated
1113
admin/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -10,6 +10,7 @@
|
|||
"build": "node ace build",
|
||||
"dev": "node ace serve --hmr",
|
||||
"test": "node ace test",
|
||||
"test:unit": "vitest run",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
|
|
@ -47,6 +48,8 @@
|
|||
"@japa/runner": "^4.2.0",
|
||||
"@swc/core": "1.11.24",
|
||||
"@tanstack/eslint-plugin-query": "^5.81.2",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/dockerode": "^3.3.41",
|
||||
"@types/luxon": "^3.6.2",
|
||||
"@types/node": "^22.15.18",
|
||||
|
|
@ -55,10 +58,12 @@
|
|||
"@types/stopword": "^2.0.3",
|
||||
"eslint": "^9.26.0",
|
||||
"hot-hook": "^0.4.0",
|
||||
"jsdom": "^29.0.1",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-node-maintained": "^10.9.5",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^6.4.1"
|
||||
"vite": "^6.4.1",
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adonisjs/auth": "^9.4.0",
|
||||
|
|
|
|||
64
admin/tests/unit/utils/fs.test.ts
Normal file
64
admin/tests/unit/utils/fs.test.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { determineFileType, matchesDevice, sanitizeFilename } from '#app/utils/fs'
|
||||
|
||||
describe('determineFileType', () => {
|
||||
it('identifica arquivo JPG como image', () => {
|
||||
expect(determineFileType('photo.jpg')).toBe('image')
|
||||
})
|
||||
|
||||
it('identifica arquivo PNG maiúsculo como image', () => {
|
||||
expect(determineFileType('photo.PNG')).toBe('image')
|
||||
})
|
||||
|
||||
it('identifica arquivo PDF', () => {
|
||||
expect(determineFileType('doc.pdf')).toBe('pdf')
|
||||
})
|
||||
|
||||
it('identifica arquivo Markdown como text', () => {
|
||||
expect(determineFileType('readme.md')).toBe('text')
|
||||
})
|
||||
|
||||
it('identifica arquivo TXT como text', () => {
|
||||
expect(determineFileType('readme.txt')).toBe('text')
|
||||
})
|
||||
|
||||
it('identifica arquivo ZIM', () => {
|
||||
expect(determineFileType('wiki.zim')).toBe('zim')
|
||||
})
|
||||
|
||||
it('retorna unknown para extensão não reconhecida', () => {
|
||||
expect(determineFileType('archive.zip')).toBe('unknown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('matchesDevice', () => {
|
||||
it('corresponde dispositivo sda1 diretamente', () => {
|
||||
expect(matchesDevice('/dev/sda1', 'sda1')).toBe(true)
|
||||
})
|
||||
|
||||
it('corresponde dispositivo nvme diretamente', () => {
|
||||
expect(matchesDevice('/dev/nvme0n1p1', 'nvme0n1p1')).toBe(true)
|
||||
})
|
||||
|
||||
it('corresponde dispositivo LVM via device-mapper', () => {
|
||||
expect(matchesDevice('/dev/mapper/ubuntu--vg-ubuntu--lv', 'ubuntu--lv')).toBe(true)
|
||||
})
|
||||
|
||||
it('não corresponde dispositivos diferentes', () => {
|
||||
expect(matchesDevice('/dev/sda1', 'sdb1')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sanitizeFilename', () => {
|
||||
it('substitui espaços por underscores', () => {
|
||||
expect(sanitizeFilename('hello world.txt')).toBe('hello_world.txt')
|
||||
})
|
||||
|
||||
it('substitui caracteres especiais por underscores', () => {
|
||||
expect(sanitizeFilename('file@#$.pdf')).toBe('file___.pdf')
|
||||
})
|
||||
|
||||
it('mantém caracteres seguros inalterados', () => {
|
||||
expect(sanitizeFilename('safe-file_name.zip')).toBe('safe-file_name.zip')
|
||||
})
|
||||
})
|
||||
104
admin/tests/unit/utils/misc.test.ts
Normal file
104
admin/tests/unit/utils/misc.test.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { formatSpeed, toTitleCase, parseBoolean } from '#app/utils/misc'
|
||||
|
||||
describe('formatSpeed', () => {
|
||||
it('formata bytes por segundo', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = formatSpeed(500)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('500 B/s')
|
||||
})
|
||||
|
||||
it('formata kilobytes por segundo', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = formatSpeed(1024)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('1.0 KB/s')
|
||||
})
|
||||
|
||||
it('formata megabytes por segundo', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = formatSpeed(1048576)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('1.0 MB/s')
|
||||
})
|
||||
|
||||
it('formata zero bytes por segundo', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = formatSpeed(0)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('0 B/s')
|
||||
})
|
||||
})
|
||||
|
||||
describe('toTitleCase', () => {
|
||||
it('converte texto minúsculo para title case', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = toTitleCase('hello world')
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('Hello World')
|
||||
})
|
||||
|
||||
it('converte texto maiúsculo para title case', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = toTitleCase('HELLO WORLD')
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('Hello World')
|
||||
})
|
||||
|
||||
it('converte texto com caixa mista para title case', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = toTitleCase('hELLO')
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe('Hello')
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseBoolean', () => {
|
||||
it('retorna true para boolean true', () => {
|
||||
expect(parseBoolean(true)).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna false para boolean false', () => {
|
||||
expect(parseBoolean(false)).toBe(false)
|
||||
})
|
||||
|
||||
it('retorna true para string "true"', () => {
|
||||
expect(parseBoolean('true')).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna false para string "false"', () => {
|
||||
expect(parseBoolean('false')).toBe(false)
|
||||
})
|
||||
|
||||
it('retorna true para string "1"', () => {
|
||||
expect(parseBoolean('1')).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna false para string "0"', () => {
|
||||
expect(parseBoolean('0')).toBe(false)
|
||||
})
|
||||
|
||||
it('retorna true para número 1', () => {
|
||||
expect(parseBoolean(1)).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna false para número 0', () => {
|
||||
expect(parseBoolean(0)).toBe(false)
|
||||
})
|
||||
|
||||
it('retorna false para null', () => {
|
||||
expect(parseBoolean(null)).toBe(false)
|
||||
})
|
||||
|
||||
it('retorna false para undefined', () => {
|
||||
expect(parseBoolean(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
158
admin/tests/unit/utils/version.test.ts
Normal file
158
admin/tests/unit/utils/version.test.ts
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { isNewerVersion, parseMajorVersion } from '#app/utils/version'
|
||||
|
||||
describe('isNewerVersion', () => {
|
||||
it('retorna true quando major é maior', () => {
|
||||
// Cenário
|
||||
const v1 = '2.0.0'
|
||||
const v2 = '1.0.0'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna true quando minor é maior', () => {
|
||||
// Cenário
|
||||
const v1 = '1.1.0'
|
||||
const v2 = '1.0.0'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna true quando patch é maior', () => {
|
||||
// Cenário
|
||||
const v1 = '1.0.1'
|
||||
const v2 = '1.0.0'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna false quando major é menor', () => {
|
||||
// Cenário
|
||||
const v1 = '1.0.0'
|
||||
const v2 = '2.0.0'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(false)
|
||||
})
|
||||
|
||||
it('retorna false quando versões são iguais', () => {
|
||||
// Cenário
|
||||
const v1 = '1.0.0'
|
||||
const v2 = '1.0.0'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(false)
|
||||
})
|
||||
|
||||
it('retorna true com prefixo v', () => {
|
||||
// Cenário
|
||||
const v1 = 'v2.0.0'
|
||||
const v2 = 'v1.0.0'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna false para pre-release sem flag includePreReleases', () => {
|
||||
// Cenário
|
||||
const v1 = '2.0.0-rc.1'
|
||||
const v2 = '1.0.0'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(false)
|
||||
})
|
||||
|
||||
it('retorna true para pre-release com flag includePreReleases', () => {
|
||||
// Cenário
|
||||
const v1 = '2.0.0-rc.1'
|
||||
const v2 = '1.0.0'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2, true)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna true quando GA é comparado com RC da mesma versão', () => {
|
||||
// Cenário
|
||||
const v1 = '1.0.0'
|
||||
const v2 = '1.0.0-rc.1'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(true)
|
||||
})
|
||||
|
||||
it('retorna true quando RC maior é comparado com RC menor', () => {
|
||||
// Cenário
|
||||
const v1 = '1.0.0-rc.2'
|
||||
const v2 = '1.0.0-rc.1'
|
||||
|
||||
// Ação
|
||||
const resultado = isNewerVersion(v1, v2, true)
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseMajorVersion', () => {
|
||||
it('extrai major de tag com prefixo v', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = parseMajorVersion('v3.8.1')
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(3)
|
||||
})
|
||||
|
||||
it('extrai major de tag sem prefixo v', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = parseMajorVersion('10.19.4')
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(10)
|
||||
})
|
||||
|
||||
it('retorna 0 para tag inválida', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = parseMajorVersion('invalid')
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(0)
|
||||
})
|
||||
|
||||
it('retorna 0 para versão 0.x.x', () => {
|
||||
// Cenário / Ação
|
||||
const resultado = parseMajorVersion('v0.1.0')
|
||||
|
||||
// Validação
|
||||
expect(resultado).toBe(0)
|
||||
})
|
||||
})
|
||||
56
admin/tests/unit/validators/common.test.ts
Normal file
56
admin/tests/unit/validators/common.test.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { assertNotPrivateUrl } from '#app/validators/common'
|
||||
|
||||
describe('assertNotPrivateUrl', () => {
|
||||
it('permite URL pública HTTPS', () => {
|
||||
// Cenário
|
||||
const url = 'https://example.com/file.zim'
|
||||
|
||||
// Ação / Validação
|
||||
expect(() => assertNotPrivateUrl(url)).not.toThrow()
|
||||
})
|
||||
|
||||
it('permite URL RFC1918 192.168.x.x (LAN appliance)', () => {
|
||||
// Cenário
|
||||
const url = 'http://192.168.1.100:8080/file.zim'
|
||||
|
||||
// Ação / Validação
|
||||
expect(() => assertNotPrivateUrl(url)).not.toThrow()
|
||||
})
|
||||
|
||||
it('permite URL RFC1918 10.x.x.x (LAN appliance)', () => {
|
||||
// Cenário
|
||||
const url = 'http://10.0.0.1/file.zim'
|
||||
|
||||
// Ação / Validação
|
||||
expect(() => assertNotPrivateUrl(url)).not.toThrow()
|
||||
})
|
||||
|
||||
it('bloqueia localhost', () => {
|
||||
// Cenário
|
||||
const url = 'http://localhost/file'
|
||||
|
||||
// Ação / Validação
|
||||
expect(() => assertNotPrivateUrl(url)).toThrow()
|
||||
})
|
||||
|
||||
it('bloqueia 127.0.0.1 (loopback)', () => {
|
||||
expect(() => assertNotPrivateUrl('http://127.0.0.1/file')).toThrow()
|
||||
})
|
||||
|
||||
it('bloqueia 127.0.0.2 (loopback alternativo)', () => {
|
||||
expect(() => assertNotPrivateUrl('http://127.0.0.2/file')).toThrow()
|
||||
})
|
||||
|
||||
it('bloqueia 0.0.0.0', () => {
|
||||
expect(() => assertNotPrivateUrl('http://0.0.0.0/file')).toThrow()
|
||||
})
|
||||
|
||||
it('bloqueia endereço de metadados cloud 169.254.169.254', () => {
|
||||
expect(() => assertNotPrivateUrl('http://169.254.169.254/metadata')).toThrow()
|
||||
})
|
||||
|
||||
it('bloqueia IPv6 loopback [::1]', () => {
|
||||
expect(() => assertNotPrivateUrl('http://[::1]/file')).toThrow()
|
||||
})
|
||||
})
|
||||
20
admin/vitest.config.ts
Normal file
20
admin/vitest.config.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { defineConfig } from 'vitest/config'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
include: [
|
||||
'inertia/__tests__/**/*.test.ts',
|
||||
'tests/unit/**/*.test.ts',
|
||||
],
|
||||
globals: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': resolve(__dirname, 'inertia'),
|
||||
'#app': resolve(__dirname, 'app'),
|
||||
'#validators': resolve(__dirname, 'app/validators'),
|
||||
},
|
||||
},
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user