Pular para o conteúdo

Aplicar regras e validar seu modelo

Às vezes você pode querer aplicar regras personalizadas ao seu modelo ou validar sua consistência. Aqui vai uma receita simples de como fazer isso.

Neste exemplo, vamos usar o Vitest junto com a API do LikeC4. A API do LikeC4 oferece métodos para consultar e percorrer o modelo, perfeita para escrever testes que reforçam suas regras.

npm i likec4 vitest

Suponha que queremos exigir que todo elemento do tipo app tenha uma technology especificada.

test/metadata.spec.ts
import { LikeC4 } from 'likec4'
import { test } from 'vitest'
// Initialize and compute LikeC4 Model
const likec4 = await LikeC4.fromWorkspace('..')
const model = await likec4.computedModel()
// With `test.for` we generate tests for each element of kind `app`
// This improves the output, showing each test failure separately
test.for(
model
// Select elements of kind `app`
.elementsWhere({ kind: 'app' })
// Map to array of [id, element] tuples, we need it for test names
.map(e => [e.id, e] as const)
.toArray(),
)('app "%s" has technology', ([, e], { expect }) => {
expect(e.technology).toBeTruthy()
})
// Or we can use `expect.soft` to accumulate all errors
test('elements of kind `app` have technology', ({ expect }) => {
expect.hasAssertions()
for (const app of model.elements()) {
if (app.kind !== 'app') continue // Skip non-app elements
expect.soft(app.technology, `app ${app.id} has no technology`).toBeTruthy()
}
})

Podemos otimizar nossos testes pré-gerando o modelo no global setup:

global-setup.ts
import { execSync } from 'node:child_process'
export default function() {
execSync('npx likec4 gen model -o ./test/likec4-model.ts', {
stdio: 'inherit'
})
}

O modelo gerado é totalmente tipado, oferecendo verificação de tipos e autocompletar nos testes:

test/metadata.spec.ts
import { likec4model } from './likec4-model'
import { test } from 'vitest'
test('Relationships should have metadata', ({ expect }) => {
expect.hasAssertions()
for (const r of likec4model.relationships()) {
expect.soft(
r.getMetadata('key'), // here we get type checking
`Relationship ${r.source.id} -> ${r.target.id} has no metadata`
).toBeDefined()
}
})

Podemos ir além e usar o contexto de teste para melhorar nossa experiência:

test/likec4test.ts
import { likec4model } from './likec4-model'
import { test } from 'vitest'
interface LikeC4TestFixtures {
likec4: typeof likec4model
}
// This wil be our test function with the model in the context
export const likec4test = test.extend<LikeC4TestFixtures>({
likec4: async ({}, use) => {
await use(likec4model)
},
})

Agora refatore os testes para usá-la:

test/metadata.spec.ts
import { likec4test } from './likec4test'
likec4test('Relationships should have metadata', ({ expect, likec4 }) => {
expect.hasAssertions()
for (const r of likec4.relationships()) {
expect.soft(
r.getMetadata('key'), // here we get type checking
`Relationship ${r.source.id} -> ${r.target.id} has no metadata`
).toBeDefined()
}
})

Essa abordagem facilita aplicar restrições personalizadas e validar a consistência do seu modelo. Executar essas verificações no pipeline de CI é rápido e fornece feedback imediato quando o modelo quebra suas regras.