This commit is contained in:
Luís Miguel 2026-03-27 07:14:54 -03:00 committed by GitHub
commit 27bf6efaa0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1893 additions and 2 deletions

View 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')
})
})

View 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()
})
})

View 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

File diff suppressed because it is too large Load Diff

View File

@ -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",

View 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')
})
})

View 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)
})
})

View 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)
})
})

View 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
View 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'),
},
},
})