Struktura podle zasad: rozdeleni souboru, lint, testy, prisny TypeScript
Projekt srovnan se zasadami v D:\GitHubRepository\CLAUDE.md bez zmeny chovani.
Struktura: scripts/ (skripty konektoru) -> connectors/, src/scripts ->
src/runtime/scripts; src/index.ts jen startuje, novy src/app.ts s createApp();
routes/dashboard.ts a routes/settings.ts rozdeleny do slozek; openapi.ts
rozdelen na openapi/{index,helpers,components} a paths/* (98 cest overeno
shodnych); ticketStore, automationStore a services jsou fasady nad slozkami
data/tickets, data/automations a data/services/catalog. process.env se cte
jen v config.ts. Web: hooky v hooks/, sdilena ui/Table a ui/ServiceIcon,
surove inputy nahrazeny komponentami, sedm velkych souboru rozdeleno.
Nastroje: eslint (typescript-eslint, react-hooks v7), prettier, editorconfig,
nvmrc, .env.example, vitest; skripty lint, format, test. Lint je cisty bez
jedineho eslint-disable (nove hooky useLatest a useSyncFromSource, odvozeny
stav misto setState v effectu). noUncheckedIndexedAccess v obou tsconfig,
84 mist zuzeno bez non-null operatoru; odhalilo zalohu backoffu fronty pri
nule pokusu a Retry-After NaN pri max 0. Cely kod naformatovan prettierem.
Testy: 8 souboru, 105 testu (prava, viditelnost, podminky a opakovani
v executoru, redaktor tajemstvi, sitove guardy, migrace resitelu, tickety,
health a prihlaseni pres supertest). Testy odhalily dve chyby ve vyhodnoceni
podminek, obe opravene: chybejici castka se porovnavala jako nula a podminka
nad vystupem druheho kroku cetla hodnotu prvniho se stejnym nazvem.
Pojmenovane konstanty misto magickych hodnot, ctx.util.base64 pro skripty
konektoru, README a dokumentace aktualizovany vcetne znamych odchylek.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Aplikace pres HTTP (src/app.ts): health, prihlaseni a prava.
|
||||
*
|
||||
* Data se nacitaji stejnym bootstrapem jako v `src/index.ts`, jen v pameti
|
||||
* (`DATA_DIR` prazdny, viz tests/setup.ts). Zadny port se neotevira,
|
||||
* supertest vola aplikaci primo.
|
||||
*/
|
||||
|
||||
import request from 'supertest';
|
||||
import { beforeAll, describe, expect, test } from 'vitest';
|
||||
import type { Express } from 'express';
|
||||
import { createApp } from '../../src/app.js';
|
||||
import { bootstrapData } from '../../src/data/bootstrap.js';
|
||||
|
||||
let app: Express;
|
||||
|
||||
beforeAll(async () => {
|
||||
const mode = await bootstrapData({ databaseReady: false });
|
||||
expect(mode).toBe('memory');
|
||||
app = createApp();
|
||||
});
|
||||
|
||||
async function login(email: string, password: string): Promise<request.Response> {
|
||||
return request(app).post('/api/auth/login').send({ email, password });
|
||||
}
|
||||
|
||||
describe('health', () => {
|
||||
test('GET /health odpovi 200 bez databaze', async () => {
|
||||
const response = await request(app).get('/health');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.status).toBe('ok');
|
||||
expect(typeof response.body.uptimeSec).toBe('number');
|
||||
});
|
||||
|
||||
test('GET /health/ready hlasi uloziste v pameti', async () => {
|
||||
const response = await request(app).get('/health/ready');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.database.enabled).toBe(false);
|
||||
});
|
||||
|
||||
test('neznama API cesta vraci JSON 404', async () => {
|
||||
const response = await request(app).get('/api/neexistuje');
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body.error).toBe('not_found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('prihlaseni a prava', () => {
|
||||
test('bez tokenu je dashboard 401', async () => {
|
||||
const response = await request(app).get('/api/dashboard/access');
|
||||
expect(response.status).toBe(401);
|
||||
expect(response.body.error).toBe('unauthorized');
|
||||
});
|
||||
|
||||
test('neplatny token je 401', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/dashboard/access')
|
||||
.set('Authorization', 'Bearer neplatny');
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
|
||||
test('spatne heslo i neznamy e-mail daji stejnou odpoved', async () => {
|
||||
const wrong = await login('admin@automia.cz', 'spatne');
|
||||
const unknown = await login('nikdo@automia.cz', 'demo1234');
|
||||
expect(wrong.status).toBe(401);
|
||||
expect(unknown.status).toBe(401);
|
||||
expect(wrong.body).toEqual(unknown.body);
|
||||
});
|
||||
|
||||
test('po prihlaseni vraci /access role a pohledy', async () => {
|
||||
const loginResponse = await login('admin@automia.cz', 'demo1234');
|
||||
expect(loginResponse.status).toBe(200);
|
||||
expect(typeof loginResponse.body.token).toBe('string');
|
||||
// Hash hesla nikdy neopousti server.
|
||||
expect(loginResponse.body.user.passwordHash).toBeUndefined();
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/dashboard/access')
|
||||
.set('Authorization', `Bearer ${loginResponse.body.token}`);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.platformAdmin).toBe(true);
|
||||
expect(response.body.roleNames).toEqual(['Správce firmy']);
|
||||
expect(response.body.scopes).toEqual(['all', 'tenant', 'mine']);
|
||||
expect(response.body.defaultTenantId).toBe('tnt_automia');
|
||||
expect(response.body.permissions).toContain('tenant.manage');
|
||||
});
|
||||
|
||||
test('clen dvou firem dostane prava za firmu z query', async () => {
|
||||
const token = (await login('karel.vomacka@automia.cz', 'demo1234')).body.token as string;
|
||||
|
||||
const automia = await request(app)
|
||||
.get('/api/dashboard/access')
|
||||
.set('Authorization', `Bearer ${token}`);
|
||||
expect(automia.status).toBe(200);
|
||||
expect(automia.body.roleNames).toEqual(['Řešitel']);
|
||||
expect(automia.body.permissions).not.toContain('user.manage');
|
||||
|
||||
const nordis = await request(app)
|
||||
.get('/api/dashboard/access?tenantId=tnt_nordis')
|
||||
.set('Authorization', `Bearer ${token}`);
|
||||
expect(nordis.body.roleNames).toEqual(['Správce firmy']);
|
||||
expect(nordis.body.permissions).toContain('user.manage');
|
||||
|
||||
// Cizi firma: prava se spocitaji za ni, ale clenstvi nesedi, takze nic.
|
||||
const foreign = await request(app)
|
||||
.get('/api/dashboard/tickets?tenantId=tnt_logitrans')
|
||||
.set('Authorization', `Bearer ${token}`);
|
||||
expect(foreign.status).toBe(404);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user