negyedik gyakorlat + megoldasok
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# Server Configuration
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
|
||||
# Database Configuration (PostgreSQL + Prisma)
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/cors_di_app?schema=public"
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
# Ethereal Email Configuration (https://ethereal.email/create)
|
||||
ETHEREAL_USER=your-ethereal-user@ethereal.email
|
||||
ETHEREAL_PASS=your-ethereal-password
|
||||
|
||||
# CORS Configuration (comma-separated origins)
|
||||
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001
|
||||
@@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,829 @@
|
||||
# 📚 SEGÍTSÉG - Példakódok és Magyarázatok
|
||||
|
||||
## Tartalomjegyzék
|
||||
|
||||
1. [DI Container Implementálása](#1-di-container-implementálása)
|
||||
2. [CORS Middleware Implementálása](#2-cors-middleware-implementálása)
|
||||
3. [Email Service Implementálása](#3-email-service-implementálása)
|
||||
4. [Cookie-Based JWT Használata](#4-cookie-based-jwt-használata)
|
||||
5. [Tesztelési Példák](#5-tesztelési-példák)
|
||||
6. [Gyakori Hibák és Megoldások](#6-gyakori-hibák-és-megoldások)
|
||||
|
||||
---
|
||||
|
||||
## 1. DI Container Implementálása
|
||||
|
||||
### 📁 Fájl: `src/application/Container.js`
|
||||
|
||||
### Teljes megoldás magyarázattal:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Dependency Injection Container
|
||||
* Supports singleton, transient, and scoped lifetimes
|
||||
*/
|
||||
class Container {
|
||||
constructor() {
|
||||
// 1. Singleton instance-ok tárolása (egyszer létrehozott objektumok)
|
||||
this.services = new Map();
|
||||
|
||||
// 2. Factory függvények tárolása (objektumokat létrehozó függvények)
|
||||
this.factories = new Map();
|
||||
|
||||
// 3. Lifecycle típusok tárolása (singleton/transient/scoped)
|
||||
this.lifetimes = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Service regisztrálása
|
||||
* @param {string} name - Service neve
|
||||
* @param {Function} factory - Factory függvény ami a service-t létrehozza
|
||||
* @param {string} lifetime - 'singleton' | 'transient' | 'scoped'
|
||||
*/
|
||||
register(name, factory, lifetime = 'singleton') {
|
||||
// 4. Factory függvény eltárolása
|
||||
this.factories.set(name, factory);
|
||||
|
||||
// 5. Lifetime típus eltárolása
|
||||
this.lifetimes.set(name, lifetime);
|
||||
|
||||
// 6. Ha singleton, azonnal példányosítjuk és eltároljuk
|
||||
if (lifetime === 'singleton') {
|
||||
const instance = factory();
|
||||
this.services.set(name, instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Service lekérése
|
||||
* @param {string} name - Service neve
|
||||
* @param {Map} scope - Opcionális scope Map scoped lifetime-hoz
|
||||
* @returns {any} Service instance
|
||||
*/
|
||||
resolve(name, scope = null) {
|
||||
// 7. Scoped lifecycle kezelése
|
||||
if (this.lifetimes.get(name) === 'scoped' && scope) {
|
||||
// Ha már van a scope-ban, azt adjuk vissza
|
||||
if (scope.has(name)) {
|
||||
return scope.get(name);
|
||||
}
|
||||
|
||||
// Ha nincs még, létrehozzuk és eltároljuk a scope-ban
|
||||
const instance = this.factories.get(name)();
|
||||
scope.set(name, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 8. Singleton lifecycle - mindig ugyanazt az instance-t adjuk vissza
|
||||
if (this.lifetimes.get(name) === 'singleton') {
|
||||
return this.services.get(name);
|
||||
}
|
||||
|
||||
// 9. Transient lifecycle - mindig új instance-t hozunk létre
|
||||
if (this.lifetimes.get(name) === 'transient') {
|
||||
return this.factories.get(name)();
|
||||
}
|
||||
|
||||
// 10. Ha nincs regisztrálva a service, hibát dobunk
|
||||
throw new Error(`Service '${name}' is not registered`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Új scope létrehozása scoped lifecycle-hoz (pl. request-enkénti instance-ok)
|
||||
* @returns {Object} Scope objektum resolve metódussal
|
||||
*/
|
||||
createScope() {
|
||||
// 11. Új Map létrehozása a scoped instance-oknak
|
||||
const scopeMap = new Map();
|
||||
|
||||
// 12. Visszaadunk egy objektumot ami tartalmaz egy resolve metódust
|
||||
return {
|
||||
resolve: (name) => this.resolve(name, scopeMap)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Container;
|
||||
```
|
||||
|
||||
### 💡 Használati példák:
|
||||
|
||||
#### Singleton (egy instance az egész alkalmazásban)
|
||||
|
||||
```javascript
|
||||
const container = new Container();
|
||||
|
||||
// Singleton PrismaClient (egy kapcsolat az egész app-ban)
|
||||
container.register('PrismaClient', () => {
|
||||
return new PrismaClient();
|
||||
}, 'singleton');
|
||||
|
||||
// Minden resolve ugyanazt az instance-t adja vissza
|
||||
const prisma1 = container.resolve('PrismaClient');
|
||||
const prisma2 = container.resolve('PrismaClient');
|
||||
console.log(prisma1 === prisma2); // true - ugyanaz az objektum!
|
||||
```
|
||||
|
||||
#### Transient (minden resolve új instance)
|
||||
|
||||
```javascript
|
||||
// Transient logger (minden híváshoz új)
|
||||
container.register('Logger', () => {
|
||||
return {
|
||||
id: Math.random(),
|
||||
log: (msg) => console.log(`[${new Date().toISOString()}] ${msg}`)
|
||||
};
|
||||
}, 'transient');
|
||||
|
||||
const logger1 = container.resolve('Logger');
|
||||
const logger2 = container.resolve('Logger');
|
||||
console.log(logger1 === logger2); // false - különböző objektumok!
|
||||
console.log(logger1.id !== logger2.id); // true - különböző ID-k
|
||||
```
|
||||
|
||||
#### Scoped (request szinten megosztott)
|
||||
|
||||
```javascript
|
||||
// Scoped RequestContext
|
||||
container.register('RequestContext', () => {
|
||||
return {
|
||||
id: Math.random(),
|
||||
user: null,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}, 'scoped');
|
||||
|
||||
// Első request scope
|
||||
const scope1 = container.createScope();
|
||||
const ctx1a = scope1.resolve('RequestContext');
|
||||
const ctx1b = scope1.resolve('RequestContext');
|
||||
console.log(ctx1a === ctx1b); // true - ugyanaz a scope-on belül!
|
||||
|
||||
// Második request scope
|
||||
const scope2 = container.createScope();
|
||||
const ctx2 = scope2.resolve('RequestContext');
|
||||
console.log(ctx1a === ctx2); // false - különböző scope-ok!
|
||||
```
|
||||
|
||||
### 🧪 Tesztelés:
|
||||
|
||||
```javascript
|
||||
// tests/unit/application/Container.test.js
|
||||
const Container = require('../../src/application/Container');
|
||||
|
||||
describe('Container', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
container = new Container();
|
||||
});
|
||||
|
||||
test('singleton - should return same instance', () => {
|
||||
container.register('TestService', () => ({ id: Math.random() }), 'singleton');
|
||||
|
||||
const instance1 = container.resolve('TestService');
|
||||
const instance2 = container.resolve('TestService');
|
||||
|
||||
expect(instance1).toBe(instance2);
|
||||
expect(instance1.id).toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('transient - should return different instances', () => {
|
||||
container.register('TestService', () => ({ id: Math.random() }), 'transient');
|
||||
|
||||
const instance1 = container.resolve('TestService');
|
||||
const instance2 = container.resolve('TestService');
|
||||
|
||||
expect(instance1).not.toBe(instance2);
|
||||
expect(instance1.id).not.toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('scoped - should return same instance within scope', () => {
|
||||
container.register('TestService', () => ({ id: Math.random() }), 'scoped');
|
||||
|
||||
const scope = container.createScope();
|
||||
const instance1 = scope.resolve('TestService');
|
||||
const instance2 = scope.resolve('TestService');
|
||||
|
||||
expect(instance1).toBe(instance2);
|
||||
expect(instance1.id).toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('scoped - different scopes should have different instances', () => {
|
||||
container.register('TestService', () => ({ id: Math.random() }), 'scoped');
|
||||
|
||||
const scope1 = container.createScope();
|
||||
const scope2 = container.createScope();
|
||||
|
||||
const instance1 = scope1.resolve('TestService');
|
||||
const instance2 = scope2.resolve('TestService');
|
||||
|
||||
expect(instance1).not.toBe(instance2);
|
||||
});
|
||||
|
||||
test('should throw error for unregistered service', () => {
|
||||
expect(() => container.resolve('NonExistent')).toThrow(
|
||||
"Service 'NonExistent' is not registered"
|
||||
);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. CORS Middleware Implementálása
|
||||
|
||||
### 📁 Fájl: `src/api/middlewares/corsMiddleware.js`
|
||||
|
||||
### Teljes megoldás:
|
||||
|
||||
```javascript
|
||||
const cors = require('cors');
|
||||
|
||||
// Engedélyezett origin-ek whitelist-je (környezeti változóból)
|
||||
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173', // Vite default port
|
||||
'http://localhost:5174',
|
||||
'http://localhost:4200' // Angular default port
|
||||
];
|
||||
|
||||
/**
|
||||
* CORS Configuration
|
||||
* Whitelist-based origin validation
|
||||
*/
|
||||
const corsOptions = {
|
||||
/**
|
||||
* Origin ellenőrzés
|
||||
* @param {string} origin - Request origin
|
||||
* @param {Function} callback - Callback(error, allowed)
|
||||
*/
|
||||
origin: function (origin, callback) {
|
||||
// 1. Ha nincs origin (backend-to-backend, Postman, curl)
|
||||
// Ezeket általában engedélyezzük development-ben
|
||||
if (!origin) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// 2. Ha az origin benne van az allowedOrigins listában
|
||||
if (allowedOrigins.includes(origin)) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// 3. Egyébként tiltjuk CORS hibával
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
},
|
||||
|
||||
// Cookie és Authorization header engedélyezése
|
||||
credentials: true,
|
||||
|
||||
// Engedélyezett HTTP metódusok
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||
|
||||
// Engedélyezett header-ek
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
|
||||
// Response header-ek amiket a frontend láthat
|
||||
exposedHeaders: ['Set-Cookie'],
|
||||
|
||||
// Preflight cache idő (másodpercben)
|
||||
maxAge: 600 // 10 perc
|
||||
};
|
||||
|
||||
module.exports = cors(corsOptions);
|
||||
```
|
||||
|
||||
### 💡 Használat:
|
||||
|
||||
#### `.env` konfiguráció:
|
||||
|
||||
```env
|
||||
# Development
|
||||
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||
|
||||
# Production
|
||||
ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com,https://admin.myapp.com
|
||||
```
|
||||
|
||||
#### Aktiválás `server.js`-ben:
|
||||
|
||||
```javascript
|
||||
// src/api/server.js
|
||||
const corsMiddleware = require('./middlewares/corsMiddleware');
|
||||
|
||||
// Middleware chain
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(cookieParser());
|
||||
app.use(corsMiddleware); // <-- CORS middleware hozzáadása
|
||||
```
|
||||
|
||||
### 🧪 Tesztelés cURL-lel:
|
||||
|
||||
```bash
|
||||
# Engedélyezett origin
|
||||
curl -H "Origin: http://localhost:3000" \
|
||||
-H "Access-Control-Request-Method: POST" \
|
||||
-H "Access-Control-Request-Headers: Content-Type" \
|
||||
-X OPTIONS \
|
||||
http://localhost:3000/api/users
|
||||
|
||||
# Sikeres válasz:
|
||||
# Access-Control-Allow-Origin: http://localhost:3000
|
||||
# Access-Control-Allow-Credentials: true
|
||||
|
||||
# Tiltott origin
|
||||
curl -H "Origin: http://malicious-site.com" \
|
||||
-X GET \
|
||||
http://localhost:3000/api/users
|
||||
|
||||
# Hiba válasz: "Not allowed by CORS"
|
||||
```
|
||||
|
||||
### 🧪 Frontend tesztelés:
|
||||
|
||||
```javascript
|
||||
// React/Vue/Angular frontend
|
||||
fetch('http://localhost:3000/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include', // Cookie küldés/fogadás engedélyezése
|
||||
body: JSON.stringify({ email, password })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => console.log('Login sikeres:', data))
|
||||
.catch(err => console.error('CORS hiba:', err));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Email Service Implementálása
|
||||
|
||||
### 📁 Fájl: `src/application/services/EmailService.js`
|
||||
|
||||
### Teljes megoldás:
|
||||
|
||||
```javascript
|
||||
const nodemailer = require('nodemailer');
|
||||
const handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Email Service
|
||||
* Nodemailer + Handlebars template based email sending
|
||||
*/
|
||||
class EmailService {
|
||||
constructor() {
|
||||
// 1. Nodemailer transport létrehozása Ethereal SMTP-vel
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: 'smtp.ethereal.email',
|
||||
port: 587,
|
||||
secure: false, // TLS
|
||||
auth: {
|
||||
user: process.env.ETHEREAL_USER || 'your-test-email@ethereal.email',
|
||||
pass: process.env.ETHEREAL_PASS || 'your-test-password'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📧 EmailService initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Welcome email küldése új regisztrált felhasználónak
|
||||
* @param {string} userEmail - Felhasználó email címe
|
||||
* @param {string} userName - Felhasználó neve
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async sendWelcomeEmail(userEmail, userName) {
|
||||
try {
|
||||
// 2. Template fájl beolvasása
|
||||
const templatePath = path.join(__dirname, 'templates', 'welcome.hbs');
|
||||
const templateSource = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// 3. Handlebars template compile-olása
|
||||
const template = handlebars.compile(templateSource);
|
||||
|
||||
// 4. HTML generálása az adatokkal
|
||||
const html = template({
|
||||
name: userName,
|
||||
email: userEmail,
|
||||
date: new Date().toLocaleDateString('hu-HU'),
|
||||
year: new Date().getFullYear()
|
||||
});
|
||||
|
||||
// 5. Email küldése
|
||||
const info = await this.transporter.sendMail({
|
||||
from: '"Clean Architecture App" <noreply@cleanarch.com>',
|
||||
to: userEmail,
|
||||
subject: '🎉 Üdvözlünk az alkalmazásban!',
|
||||
html: html
|
||||
});
|
||||
|
||||
// 6. Ethereal preview URL kiírása
|
||||
const previewUrl = nodemailer.getTestMessageUrl(info);
|
||||
console.log('📧 Email elküldve:', previewUrl);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Email küldési hiba:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Password reset email (bővítési lehetőség)
|
||||
*/
|
||||
async sendPasswordResetEmail(userEmail, resetToken) {
|
||||
try {
|
||||
const templatePath = path.join(__dirname, 'templates', 'password-reset.hbs');
|
||||
const templateSource = fs.readFileSync(templatePath, 'utf-8');
|
||||
const template = handlebars.compile(templateSource);
|
||||
|
||||
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`;
|
||||
|
||||
const html = template({
|
||||
resetLink,
|
||||
expiresIn: '1 óra'
|
||||
});
|
||||
|
||||
const info = await this.transporter.sendMail({
|
||||
from: '"Clean Architecture App" <noreply@cleanarch.com>',
|
||||
to: userEmail,
|
||||
subject: '🔐 Jelszó visszaállítás',
|
||||
html: html
|
||||
});
|
||||
|
||||
console.log('📧 Password reset email:', nodemailer.getTestMessageUrl(info));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Password reset email hiba:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EmailService;
|
||||
```
|
||||
|
||||
### 📁 Email Template: `src/application/services/templates/welcome.hbs`
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Üdvözlünk!</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
background-color: #f4f7f9;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
margin-top: 0;
|
||||
font-size: 28px;
|
||||
}
|
||||
.welcome-icon {
|
||||
font-size: 48px;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
p {
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
font-size: 16px;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #e3f2fd;
|
||||
padding: 15px;
|
||||
border-left: 4px solid #2196f3;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
padding: 12px 30px;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
margin: 20px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #eee;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="welcome-icon">🎉</div>
|
||||
|
||||
<h1>Üdvözlünk, {{name}}!</h1>
|
||||
|
||||
<p>Gratulálunk! Sikeres regisztráció az alkalmazásunkban.</p>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>Regisztráció részletei:</strong><br>
|
||||
📧 Email: {{email}}<br>
|
||||
📅 Dátum: {{date}}
|
||||
</div>
|
||||
|
||||
<p>Mostantól hozzáférsz az összes funkciónkhoz:</p>
|
||||
<ul>
|
||||
<li>✅ Profil kezelés</li>
|
||||
<li>✅ Biztonságos autentikáció</li>
|
||||
<li>✅ API hozzáférés</li>
|
||||
</ul>
|
||||
|
||||
<center>
|
||||
<a href="http://localhost:3000/api/users/me" class="button">
|
||||
Profilom megtekintése
|
||||
</a>
|
||||
</center>
|
||||
|
||||
<div class="footer">
|
||||
<p>Ez egy automatikus üzenet, kérjük ne válaszolj rá.</p>
|
||||
<p>© {{year}} Clean Architecture App. Minden jog fenntartva.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 💡 Ethereal Email Beállítása:
|
||||
|
||||
1. **Menj a https://ethereal.email oldalra**
|
||||
2. **Kattints "Create Ethereal Account" gombra**
|
||||
3. **Másold ki a credentials-t:**
|
||||
|
||||
```
|
||||
Username: your-random-name@ethereal.email
|
||||
Password: your-random-password
|
||||
```
|
||||
|
||||
4. **Állítsd be a `.env` fájlban:**
|
||||
|
||||
```env
|
||||
ETHEREAL_USER=your-random-name@ethereal.email
|
||||
ETHEREAL_PASS=your-random-password
|
||||
```
|
||||
|
||||
### 🧪 Tesztelés:
|
||||
|
||||
```bash
|
||||
# Regisztráció (automatikusan küld welcome emailt)
|
||||
POST http://localhost:3000/api/auth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Test User",
|
||||
"email": "test@example.com",
|
||||
"password": "password123"
|
||||
}
|
||||
|
||||
# Console output:
|
||||
# 📧 Email elküldve: https://ethereal.email/message/XXXXXX
|
||||
```
|
||||
|
||||
**Nyisd meg a böngészőben a linket és látni fogod az emailt!**
|
||||
|
||||
---
|
||||
|
||||
## 4. Cookie-Based JWT Használata
|
||||
|
||||
### 🍪 Frontend Integration (React példa)
|
||||
|
||||
```javascript
|
||||
// authService.js
|
||||
const API_URL = 'http://localhost:3000/api';
|
||||
|
||||
export const authService = {
|
||||
async register(name, email, password) {
|
||||
const response = await fetch(`${API_URL}/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include', // FONTOS: Cookie küldés/fogadás
|
||||
body: JSON.stringify({ name, email, password })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Registration failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
// JWT automatikusan cookie-ban tárolódik!
|
||||
},
|
||||
|
||||
async login(email, password) {
|
||||
const response = await fetch(`${API_URL}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Login failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
|
||||
async logout() {
|
||||
const response = await fetch(`${API_URL}/auth/logout`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
return response.json();
|
||||
},
|
||||
|
||||
async getCurrentUser() {
|
||||
const response = await fetch(`${API_URL}/users/me`, {
|
||||
credentials: 'include' // Cookie automatikusan küldődik
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 🧪 cURL Tesztelés Cookie-val:
|
||||
|
||||
```bash
|
||||
# 1. Login + Cookie mentése
|
||||
curl -c cookies.txt -X POST http://localhost:3000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"test@example.com","password":"password123"}'
|
||||
|
||||
# 2. Protected endpoint hívása a cookie-val
|
||||
curl -b cookies.txt http://localhost:3000/api/users/me
|
||||
|
||||
# 3. Logout
|
||||
curl -b cookies.txt -c cookies.txt -X POST http://localhost:3000/api/auth/logout
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Tesztelési Példák
|
||||
|
||||
### Controller Teszt Cookie-val:
|
||||
|
||||
```javascript
|
||||
// tests/unit/controllers/AuthController.test.js
|
||||
const AuthController = require('../../src/api/controllers/AuthController');
|
||||
|
||||
describe('AuthController - Cookie-based JWT', () => {
|
||||
let authController;
|
||||
let mockRegisterHandler;
|
||||
let mockLoginHandler;
|
||||
let mockJwtService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRegisterHandler = { handle: jest.fn() };
|
||||
mockLoginHandler = { handle: jest.fn() };
|
||||
mockJwtService = {
|
||||
getCookieName: jest.fn().mockReturnValue('auth_token'),
|
||||
getCookieOptions: jest.fn().mockReturnValue({
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
sameSite: 'strict'
|
||||
})
|
||||
};
|
||||
|
||||
authController = new AuthController(
|
||||
mockRegisterHandler,
|
||||
mockLoginHandler,
|
||||
mockJwtService
|
||||
);
|
||||
});
|
||||
|
||||
test('login should set JWT in cookie', async () => {
|
||||
const mockReq = {
|
||||
body: { email: 'test@example.com', password: 'password123' }
|
||||
};
|
||||
const mockRes = {
|
||||
cookie: jest.fn(),
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn()
|
||||
};
|
||||
|
||||
mockLoginHandler.handle.mockResolvedValue({
|
||||
user: { id: 1, email: 'test@example.com' },
|
||||
token: 'mock_jwt_token'
|
||||
});
|
||||
|
||||
await authController.login(mockReq, mockRes);
|
||||
|
||||
// Cookie beállítás ellenőrzése
|
||||
expect(mockRes.cookie).toHaveBeenCalledWith(
|
||||
'auth_token',
|
||||
'mock_jwt_token',
|
||||
expect.objectContaining({
|
||||
httpOnly: true,
|
||||
sameSite: 'strict'
|
||||
})
|
||||
);
|
||||
|
||||
// Response csak user-t tartalmaz, token nincs a body-ban
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
message: 'Login successful',
|
||||
data: {
|
||||
user: { id: 1, email: 'test@example.com' }
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Gyakori Hibák és Megoldások
|
||||
|
||||
### ❌ Hiba: "Service 'PrismaClient' is not registered"
|
||||
|
||||
**Megoldás:**
|
||||
```javascript
|
||||
// Ellenőrizd a server.js-ben:
|
||||
container.register('PrismaClient', () => {
|
||||
return databaseConnection.getClient();
|
||||
}, 'singleton');
|
||||
```
|
||||
|
||||
### ❌ Hiba: "Not allowed by CORS"
|
||||
|
||||
**Megoldás:**
|
||||
```env
|
||||
# .env fájlban add meg a frontend origin-t:
|
||||
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||
```
|
||||
|
||||
### ❌ Hiba: "No token provided in cookies"
|
||||
|
||||
**Megoldás:**
|
||||
```javascript
|
||||
// Frontend-en használd a credentials: 'include'-ot:
|
||||
fetch('http://localhost:3000/api/users/me', {
|
||||
credentials: 'include'
|
||||
});
|
||||
```
|
||||
|
||||
### ❌ Hiba: Email nem megy ki
|
||||
|
||||
**Megoldás:**
|
||||
1. Ellenőrizd az Ethereal credentials-t (`.env`)
|
||||
2. Hozz létre új Ethereal account-ot: https://ethereal.email
|
||||
3. Ellenőrizd a template fájl elérési útját:
|
||||
```javascript
|
||||
const templatePath = path.join(__dirname, 'templates', 'welcome.hbs');
|
||||
```
|
||||
|
||||
### ❌ Hiba: Container circular dependency
|
||||
|
||||
**Megoldás:**
|
||||
```javascript
|
||||
// Használj lazy loading-ot:
|
||||
container.register('ServiceA', () => {
|
||||
const ServiceB = container.resolve('ServiceB');
|
||||
return new ServiceA(ServiceB);
|
||||
}, 'singleton');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 További Olvasnivalók
|
||||
|
||||
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
||||
- [Dependency Injection Pattern](https://martinfowler.com/articles/injection.html)
|
||||
- [CORS in Express](https://expressjs.com/en/resources/middleware/cors.html)
|
||||
- [Nodemailer Documentation](https://nodemailer.com/about/)
|
||||
- [Handlebars Templates](https://handlebarsjs.com/)
|
||||
- [Cookie Security Best Practices](https://owasp.org/www-community/controls/SecureCookieAttribute)
|
||||
|
||||
---
|
||||
|
||||
**Sok sikert a feladatokhoz! 🚀**
|
||||
@@ -0,0 +1,418 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<coverage generated="1772649160943" clover="3.2.0">
|
||||
<project timestamp="1772649160943" name="All files">
|
||||
<metrics statements="301" coveredstatements="1" conditionals="103" coveredconditionals="2" methods="83" coveredmethods="4" elements="487" coveredelements="7" complexity="0" loc="301" ncloc="301" packages="11" files="26" classes="26"/>
|
||||
<package name="api.controllers">
|
||||
<metrics statements="65" coveredstatements="0" conditionals="18" coveredconditionals="0" methods="9" coveredmethods="0"/>
|
||||
<file name="AuthController.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\controllers\AuthController.js">
|
||||
<metrics statements="26" coveredstatements="0" conditionals="10" coveredconditionals="0" methods="4" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="10" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="19" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="23" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="32" count="0" type="stmt"/>
|
||||
<line num="40" count="0" type="cond" truecount="0" falsecount="6"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="53" count="0" type="stmt"/>
|
||||
<line num="54" count="0" type="stmt"/>
|
||||
<line num="56" count="0" type="stmt"/>
|
||||
<line num="57" count="0" type="stmt"/>
|
||||
<line num="60" count="0" type="stmt"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="74" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="77" count="0" type="stmt"/>
|
||||
<line num="85" count="0" type="stmt"/>
|
||||
<line num="87" count="0" type="stmt"/>
|
||||
<line num="94" count="0" type="stmt"/>
|
||||
<line num="98" count="0" type="stmt"/>
|
||||
<line num="103" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="UserController.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\controllers\UserController.js">
|
||||
<metrics statements="39" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="3" count="0" type="stmt"/>
|
||||
<line num="4" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="13" count="0" type="stmt"/>
|
||||
<line num="14" count="0" type="stmt"/>
|
||||
<line num="15" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="24" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="34" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="35" count="0" type="stmt"/>
|
||||
<line num="43" count="0" type="stmt"/>
|
||||
<line num="44" count="0" type="stmt"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="47" count="0" type="stmt"/>
|
||||
<line num="53" count="0" type="stmt"/>
|
||||
<line num="61" count="0" type="stmt"/>
|
||||
<line num="62" count="0" type="stmt"/>
|
||||
<line num="63" count="0" type="stmt"/>
|
||||
<line num="65" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="69" count="0" type="stmt"/>
|
||||
<line num="70" count="0" type="stmt"/>
|
||||
<line num="72" count="0" type="stmt"/>
|
||||
<line num="77" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="78" count="0" type="stmt"/>
|
||||
<line num="86" count="0" type="stmt"/>
|
||||
<line num="87" count="0" type="stmt"/>
|
||||
<line num="88" count="0" type="stmt"/>
|
||||
<line num="90" count="0" type="stmt"/>
|
||||
<line num="91" count="0" type="stmt"/>
|
||||
<line num="93" count="0" type="stmt"/>
|
||||
<line num="98" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="99" count="0" type="stmt"/>
|
||||
<line num="104" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="api.middlewares">
|
||||
<metrics statements="17" coveredstatements="0" conditionals="4" coveredconditionals="0" methods="4" coveredmethods="0"/>
|
||||
<file name="authMiddleware.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\middlewares\authMiddleware.js">
|
||||
<metrics statements="11" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="3" count="0" type="stmt"/>
|
||||
<line num="14" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="19" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="35" count="0" type="stmt"/>
|
||||
<line num="37" count="0" type="stmt"/>
|
||||
<line num="44" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="corsMiddleware.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\middlewares\corsMiddleware.js">
|
||||
<metrics statements="4" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="4" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="6" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="scopeMiddleware.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\middlewares\scopeMiddleware.js">
|
||||
<metrics statements="2" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="6" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="api.routers">
|
||||
<metrics statements="20" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="11" coveredmethods="0"/>
|
||||
<file name="authRoutes.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\routers\authRoutes.js">
|
||||
<metrics statements="9" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="10" count="0" type="stmt"/>
|
||||
<line num="13" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="33" count="0" type="stmt"/>
|
||||
<line num="38" count="0" type="stmt"/>
|
||||
<line num="40" count="0" type="stmt"/>
|
||||
<line num="43" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="userRoutes.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\routers\userRoutes.js">
|
||||
<metrics statements="11" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="6" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="14" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="33" count="0" type="stmt"/>
|
||||
<line num="39" count="0" type="stmt"/>
|
||||
<line num="44" count="0" type="stmt"/>
|
||||
<line num="46" count="0" type="stmt"/>
|
||||
<line num="49" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="application.auth.commands">
|
||||
<metrics statements="49" coveredstatements="0" conditionals="21" coveredconditionals="0" methods="7" coveredmethods="0"/>
|
||||
<file name="LoginUserCommand.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\auth\commands\LoginUserCommand.js">
|
||||
<metrics statements="3" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="8" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="LoginUserCommandHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\auth\commands\LoginUserCommandHandler.js">
|
||||
<metrics statements="17" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="4" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="24" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="25" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="33" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="34" count="0" type="stmt"/>
|
||||
<line num="38" count="0" type="stmt"/>
|
||||
<line num="40" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="41" count="0" type="stmt"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="51" count="0" type="stmt"/>
|
||||
<line num="53" count="0" type="stmt"/>
|
||||
<line num="60" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="RegisterUserCommand.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\auth\commands\RegisterUserCommand.js">
|
||||
<metrics statements="4" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="8" count="0" type="stmt"/>
|
||||
<line num="9" count="0" type="stmt"/>
|
||||
<line num="13" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="RegisterUserCommandHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\auth\commands\RegisterUserCommandHandler.js">
|
||||
<metrics statements="25" coveredstatements="0" conditionals="13" coveredconditionals="0" methods="3" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="4" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="13" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="25" count="0" type="cond" truecount="0" falsecount="5"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="30" count="0" type="stmt"/>
|
||||
<line num="34" count="0" type="stmt"/>
|
||||
<line num="35" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="36" count="0" type="stmt"/>
|
||||
<line num="40" count="0" type="stmt"/>
|
||||
<line num="44" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="49" count="0" type="stmt"/>
|
||||
<line num="52" count="0" type="stmt"/>
|
||||
<line num="61" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="62" count="0" type="stmt"/>
|
||||
<line num="63" count="0" type="stmt"/>
|
||||
<line num="68" count="0" type="stmt"/>
|
||||
<line num="74" count="0" type="stmt"/>
|
||||
<line num="76" count="0" type="stmt"/>
|
||||
<line num="83" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="application.services">
|
||||
<metrics statements="34" coveredstatements="1" conditionals="22" coveredconditionals="2" methods="14" coveredmethods="4"/>
|
||||
<file name="Container.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\services\Container.js">
|
||||
<metrics statements="1" coveredstatements="1" conditionals="2" coveredconditionals="2" methods="4" coveredmethods="4"/>
|
||||
<line num="57" count="1" type="stmt"/>
|
||||
</file>
|
||||
<file name="EmailService.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\services\EmailService.js">
|
||||
<metrics statements="4" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="31" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="JwtService.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\services\JwtService.js">
|
||||
<metrics statements="29" coveredstatements="0" conditionals="20" coveredconditionals="0" methods="8" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="9" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="10" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="30" count="0" type="stmt"/>
|
||||
<line num="32" count="0" type="stmt"/>
|
||||
<line num="42" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="43" count="0" type="stmt"/>
|
||||
<line num="46" count="0" type="stmt"/>
|
||||
<line num="48" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="49" count="0" type="stmt"/>
|
||||
<line num="52" count="0" type="stmt"/>
|
||||
<line num="61" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="62" count="0" type="stmt"/>
|
||||
<line num="65" count="0" type="stmt"/>
|
||||
<line num="73" count="0" type="stmt"/>
|
||||
<line num="75" count="0" type="stmt"/>
|
||||
<line num="91" count="0" type="stmt"/>
|
||||
<line num="93" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="94" count="0" type="stmt"/>
|
||||
<line num="95" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="96" count="0" type="stmt"/>
|
||||
<line num="97" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="98" count="0" type="stmt"/>
|
||||
<line num="102" count="0" type="stmt"/>
|
||||
<line num="110" count="0" type="stmt"/>
|
||||
<line num="114" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="application.user.commands">
|
||||
<metrics statements="11" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="3" coveredmethods="0"/>
|
||||
<file name="UpdateUserProfileCommand.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\commands\UpdateUserProfileCommand.js">
|
||||
<metrics statements="3" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="8" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="UpdateUserProfileCommandHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\commands\UpdateUserProfileCommandHandler.js">
|
||||
<metrics statements="8" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="19" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="28" count="0" type="stmt"/>
|
||||
<line num="32" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="application.user.queries">
|
||||
<metrics statements="27" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="10" coveredmethods="0"/>
|
||||
<file name="GetAllUsersQuery.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetAllUsersQuery.js">
|
||||
<metrics statements="1" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetAllUsersQueryHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetAllUsersQueryHandler.js">
|
||||
<metrics statements="4" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="3" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="25" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetMeQuery.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetMeQuery.js">
|
||||
<metrics statements="2" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetMeQueryHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetMeQueryHandler.js">
|
||||
<metrics statements="8" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="23" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="31" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetUserByIdQuery.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetUserByIdQuery.js">
|
||||
<metrics statements="2" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetUserByIdQueryHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetUserByIdQueryHandler.js">
|
||||
<metrics statements="10" coveredstatements="0" conditionals="6" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="19" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="30" count="0" type="stmt"/>
|
||||
<line num="31" count="0" type="stmt"/>
|
||||
<line num="35" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="domain.irepositories">
|
||||
<metrics statements="7" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="6" coveredmethods="0"/>
|
||||
<file name="IUserRepository.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\domain\irepositories\IUserRepository.js">
|
||||
<metrics statements="7" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="6" coveredmethods="0"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="38" count="0" type="stmt"/>
|
||||
<line num="47" count="0" type="stmt"/>
|
||||
<line num="56" count="0" type="stmt"/>
|
||||
<line num="60" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="domain.models">
|
||||
<metrics statements="22" coveredstatements="0" conditionals="16" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<file name="User.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\domain\models\User.js">
|
||||
<metrics statements="22" coveredstatements="0" conditionals="16" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="8" count="0" type="stmt"/>
|
||||
<line num="9" count="0" type="stmt"/>
|
||||
<line num="10" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="24" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="25" count="0" type="stmt"/>
|
||||
<line num="28" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="32" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="33" count="0" type="stmt"/>
|
||||
<line num="36" count="0" type="stmt"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="46" count="0" type="stmt"/>
|
||||
<line num="54" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="55" count="0" type="stmt"/>
|
||||
<line num="57" count="0" type="stmt"/>
|
||||
<line num="58" count="0" type="stmt"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="76" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="infrastructure.db">
|
||||
<metrics statements="25" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<file name="DatabaseConnection.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\infrastructure\db\DatabaseConnection.js">
|
||||
<metrics statements="25" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="9" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="17" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="30" count="0" type="stmt"/>
|
||||
<line num="39" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="40" count="0" type="stmt"/>
|
||||
<line num="42" count="0" type="stmt"/>
|
||||
<line num="49" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="50" count="0" type="stmt"/>
|
||||
<line num="51" count="0" type="stmt"/>
|
||||
<line num="52" count="0" type="stmt"/>
|
||||
<line num="61" count="0" type="stmt"/>
|
||||
<line num="62" count="0" type="stmt"/>
|
||||
<line num="63" count="0" type="stmt"/>
|
||||
<line num="65" count="0" type="stmt"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="72" count="0" type="stmt"/>
|
||||
<line num="74" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="infrastructure.repositories">
|
||||
<metrics statements="24" coveredstatements="0" conditionals="4" coveredconditionals="0" methods="9" coveredmethods="0"/>
|
||||
<file name="UserRepository.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\infrastructure\repositories\UserRepository.js">
|
||||
<metrics statements="24" coveredstatements="0" conditionals="4" coveredconditionals="0" methods="9" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="10" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="24" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="25" count="0" type="stmt"/>
|
||||
<line num="28" count="0" type="stmt"/>
|
||||
<line num="37" count="0" type="stmt"/>
|
||||
<line num="41" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="42" count="0" type="stmt"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="53" count="0" type="stmt"/>
|
||||
<line num="57" count="0" type="stmt"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="74" count="0" type="stmt"/>
|
||||
<line num="83" count="0" type="stmt"/>
|
||||
<line num="93" count="0" type="stmt"/>
|
||||
<line num="102" count="0" type="stmt"/>
|
||||
<line num="103" count="0" type="stmt"/>
|
||||
<line num="106" count="0" type="stmt"/>
|
||||
<line num="108" count="0" type="stmt"/>
|
||||
<line num="119" count="0" type="stmt"/>
|
||||
<line num="130" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
</project>
|
||||
</coverage>
|
||||
File diff suppressed because one or more lines are too long
+394
@@ -0,0 +1,394 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/controllers/AuthController.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/controllers</a> AuthController.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/26</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/10</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/26</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a>
|
||||
<a name='L78'></a><a href='#L78'>78</a>
|
||||
<a name='L79'></a><a href='#L79'>79</a>
|
||||
<a name='L80'></a><a href='#L80'>80</a>
|
||||
<a name='L81'></a><a href='#L81'>81</a>
|
||||
<a name='L82'></a><a href='#L82'>82</a>
|
||||
<a name='L83'></a><a href='#L83'>83</a>
|
||||
<a name='L84'></a><a href='#L84'>84</a>
|
||||
<a name='L85'></a><a href='#L85'>85</a>
|
||||
<a name='L86'></a><a href='#L86'>86</a>
|
||||
<a name='L87'></a><a href='#L87'>87</a>
|
||||
<a name='L88'></a><a href='#L88'>88</a>
|
||||
<a name='L89'></a><a href='#L89'>89</a>
|
||||
<a name='L90'></a><a href='#L90'>90</a>
|
||||
<a name='L91'></a><a href='#L91'>91</a>
|
||||
<a name='L92'></a><a href='#L92'>92</a>
|
||||
<a name='L93'></a><a href='#L93'>93</a>
|
||||
<a name='L94'></a><a href='#L94'>94</a>
|
||||
<a name='L95'></a><a href='#L95'>95</a>
|
||||
<a name='L96'></a><a href='#L96'>96</a>
|
||||
<a name='L97'></a><a href='#L97'>97</a>
|
||||
<a name='L98'></a><a href='#L98'>98</a>
|
||||
<a name='L99'></a><a href='#L99'>99</a>
|
||||
<a name='L100'></a><a href='#L100'>100</a>
|
||||
<a name='L101'></a><a href='#L101'>101</a>
|
||||
<a name='L102'></a><a href='#L102'>102</a>
|
||||
<a name='L103'></a><a href='#L103'>103</a>
|
||||
<a name='L104'></a><a href='#L104'>104</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const RegisterUserCommand = <span class="cstat-no" title="statement not covered" >require('../../application/auth/commands/RegisterUserCommand');</span>
|
||||
const LoginUserCommand = <span class="cstat-no" title="statement not covered" >require('../../application/auth/commands/LoginUserCommand');</span>
|
||||
|
||||
/**
|
||||
* Auth Controller
|
||||
* Authentication endpoints using CQRS Commands with cookie-based JWT
|
||||
*/
|
||||
class AuthController {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(registerUserCommandHandler, loginUserCommandHandler, jwtService) {
|
||||
<span class="cstat-no" title="statement not covered" > this.registerUserCommandHandler = registerUserCommandHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.loginUserCommandHandler = loginUserCommandHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.jwtService = jwtService;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/register - User regisztráció
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync register(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const { name, email, password } = <span class="cstat-no" title="statement not covered" >req.body;</span>
|
||||
|
||||
const command = <span class="cstat-no" title="statement not covered" >new RegisterUserCommand(name, email, password);</span>
|
||||
const result = <span class="cstat-no" title="statement not covered" >await this.registerUserCommandHandler.handle(command);</span>
|
||||
|
||||
// Set JWT token in httpOnly cookie
|
||||
<span class="cstat-no" title="statement not covered" > res.cookie(</span>
|
||||
this.jwtService.getCookieName(),
|
||||
result.token,
|
||||
this.jwtService.getCookieOptions()
|
||||
);
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(201).json({</span>
|
||||
message: 'User registered successfully',
|
||||
data: {
|
||||
user: result.user
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Validációs hibák -> 400
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('required') || </span>
|
||||
error.message.includes('already exists') ||
|
||||
error.message.includes('Invalid') ||
|
||||
error.message.includes('must be') ? 400 : 500;
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/login - User bejelentkezés
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync login(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const { email, password } = <span class="cstat-no" title="statement not covered" >req.body;</span>
|
||||
|
||||
const command = <span class="cstat-no" title="statement not covered" >new LoginUserCommand(email, password);</span>
|
||||
const result = <span class="cstat-no" title="statement not covered" >await this.loginUserCommandHandler.handle(command);</span>
|
||||
|
||||
// Set JWT token in httpOnly cookie
|
||||
<span class="cstat-no" title="statement not covered" > res.cookie(</span>
|
||||
this.jwtService.getCookieName(),
|
||||
result.token,
|
||||
this.jwtService.getCookieOptions()
|
||||
);
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'Login successful',
|
||||
data: {
|
||||
user: result.user
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Validációs vagy auth hibák -> 401
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('Invalid') || </span>
|
||||
error.message.includes('required') ? 401 : 500;
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/logout - User kijelentkezés
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync logout(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
// Clear the auth cookie
|
||||
<span class="cstat-no" title="statement not covered" > res.clearCookie(this.jwtService.getCookieName(), {</span>
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'strict',
|
||||
path: '/'
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'Logout successful'
|
||||
});
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > res.status(500).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = AuthController;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/controllers/UserController.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/controllers</a> UserController.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/39</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/5</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/39</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a>
|
||||
<a name='L78'></a><a href='#L78'>78</a>
|
||||
<a name='L79'></a><a href='#L79'>79</a>
|
||||
<a name='L80'></a><a href='#L80'>80</a>
|
||||
<a name='L81'></a><a href='#L81'>81</a>
|
||||
<a name='L82'></a><a href='#L82'>82</a>
|
||||
<a name='L83'></a><a href='#L83'>83</a>
|
||||
<a name='L84'></a><a href='#L84'>84</a>
|
||||
<a name='L85'></a><a href='#L85'>85</a>
|
||||
<a name='L86'></a><a href='#L86'>86</a>
|
||||
<a name='L87'></a><a href='#L87'>87</a>
|
||||
<a name='L88'></a><a href='#L88'>88</a>
|
||||
<a name='L89'></a><a href='#L89'>89</a>
|
||||
<a name='L90'></a><a href='#L90'>90</a>
|
||||
<a name='L91'></a><a href='#L91'>91</a>
|
||||
<a name='L92'></a><a href='#L92'>92</a>
|
||||
<a name='L93'></a><a href='#L93'>93</a>
|
||||
<a name='L94'></a><a href='#L94'>94</a>
|
||||
<a name='L95'></a><a href='#L95'>95</a>
|
||||
<a name='L96'></a><a href='#L96'>96</a>
|
||||
<a name='L97'></a><a href='#L97'>97</a>
|
||||
<a name='L98'></a><a href='#L98'>98</a>
|
||||
<a name='L99'></a><a href='#L99'>99</a>
|
||||
<a name='L100'></a><a href='#L100'>100</a>
|
||||
<a name='L101'></a><a href='#L101'>101</a>
|
||||
<a name='L102'></a><a href='#L102'>102</a>
|
||||
<a name='L103'></a><a href='#L103'>103</a>
|
||||
<a name='L104'></a><a href='#L104'>104</a>
|
||||
<a name='L105'></a><a href='#L105'>105</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const GetMeQuery = <span class="cstat-no" title="statement not covered" >require('../../application/user/queries/GetMeQuery');</span>
|
||||
const GetAllUsersQuery = <span class="cstat-no" title="statement not covered" >require('../../application/user/queries/GetAllUsersQuery');</span>
|
||||
const GetUserByIdQuery = <span class="cstat-no" title="statement not covered" >require('../../application/user/queries/GetUserByIdQuery');</span>
|
||||
const UpdateUserProfileCommand = <span class="cstat-no" title="statement not covered" >require('../../application/user/commands/UpdateUserProfileCommand');</span>
|
||||
|
||||
/**
|
||||
* User Controller
|
||||
* User-related endpoints using CQRS pattern (protected by JWT)
|
||||
*/
|
||||
class UserController {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(getMeQueryHandler, getAllUsersQueryHandler, getUserByIdQueryHandler, updateUserProfileCommandHandler) {
|
||||
<span class="cstat-no" title="statement not covered" > this.getMeQueryHandler = getMeQueryHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.getAllUsersQueryHandler = getAllUsersQueryHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.getUserByIdQueryHandler = getUserByIdQueryHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.updateUserProfileCommandHandler = updateUserProfileCommandHandler;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users/me - Bejelentkezett user adatai (protected)
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync getMe(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
// req.user-t az authMiddleware tölti ki a JWT-ből
|
||||
const userId = <span class="cstat-no" title="statement not covered" >req.user.userId;</span>
|
||||
|
||||
const query = <span class="cstat-no" title="statement not covered" >new GetMeQuery(userId);</span>
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.getMeQueryHandler.handle(query);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'User retrieved successfully',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('not found') ? 404 : 500;</span>
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users - Összes user lekérése (protected)
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync getAll(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const query = <span class="cstat-no" title="statement not covered" >new GetAllUsersQuery();</span>
|
||||
const users = <span class="cstat-no" title="statement not covered" >await this.getAllUsersQueryHandler.handle(query);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'Users retrieved successfully',
|
||||
data: users,
|
||||
count: users.length
|
||||
});
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > res.status(500).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users/:id - User lekérése ID alapján (protected)
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync getById(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const { id } = <span class="cstat-no" title="statement not covered" >req.params;</span>
|
||||
const userId = <span class="cstat-no" title="statement not covered" >parseInt(id);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (isNaN(userId)) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return res.status(400).json({ error: 'Invalid user ID' });</span>
|
||||
}
|
||||
|
||||
const query = <span class="cstat-no" title="statement not covered" >new GetUserByIdQuery(userId);</span>
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.getUserByIdQueryHandler.handle(query);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'User retrieved successfully',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('not found') ? 404 : 500;</span>
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/users/me - User profil frissítése (protected)
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync updateMe(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const userId = <span class="cstat-no" title="statement not covered" >req.user.userId;</span>
|
||||
const { name } = <span class="cstat-no" title="statement not covered" >req.body;</span>
|
||||
|
||||
const command = <span class="cstat-no" title="statement not covered" >new UpdateUserProfileCommand(userId, name);</span>
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.updateUserProfileCommandHandler.handle(command);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'Profile updated successfully',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('required') ? 400 : 500;</span>
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = UserController;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/controllers</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> api/controllers</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/65</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/18</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/9</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/65</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="AuthController.js"><a href="AuthController.js.html">AuthController.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="26" class="abs low">0/26</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="10" class="abs low">0/10</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="26" class="abs low">0/26</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="UserController.js"><a href="UserController.js.html">UserController.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="39" class="abs low">0/39</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="39" class="abs low">0/39</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/middlewares/authMiddleware.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/middlewares</a> authMiddleware.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const JwtService = <span class="cstat-no" title="statement not covered" >require('../../application/services/JwtService');</span>
|
||||
|
||||
const jwtService = <span class="cstat-no" title="statement not covered" >new JwtService();</span>
|
||||
|
||||
/**
|
||||
* Authentication Middleware - JWT token ellenőrzés (Cookie-based)
|
||||
*
|
||||
* Ezt a middleware-t használd protected route-okon!
|
||||
*
|
||||
* Példa használat:
|
||||
* router.get('/me', authMiddleware, userController.getMe);
|
||||
*/
|
||||
function <span class="fstat-no" title="function not covered" >authMiddleware(</span>req, res, next) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
// 1. Token kinyerése cookieból
|
||||
const token = <span class="cstat-no" title="statement not covered" >jwtService.extractTokenFromCookies(req.cookies);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!token) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return res.status(401).json({ </span>
|
||||
error: 'Authentication required',
|
||||
message: 'No token provided in cookies'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Token verifikálása
|
||||
const decoded = <span class="cstat-no" title="statement not covered" >jwtService.verifyToken(token);</span>
|
||||
|
||||
// 3. User adatok elhelyezése req.user-ben (controller-ek használhatják)
|
||||
<span class="cstat-no" title="statement not covered" > req.user = {</span>
|
||||
userId: decoded.userId,
|
||||
email: decoded.email
|
||||
};
|
||||
|
||||
// 4. Folytatás
|
||||
<span class="cstat-no" title="statement not covered" > next();</span>
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > return res.status(401).json({ </span>
|
||||
error: 'Authentication failed',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = authMiddleware;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/middlewares/corsMiddleware.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/middlewares</a> corsMiddleware.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const cors = <span class="cstat-no" title="statement not covered" >require('cors');</span>
|
||||
|
||||
// Engedélyezett origin-ek whitelist (környezeti változóból)
|
||||
const allowedOrigins = <span class="cstat-no" title="statement not covered" >process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];</span>
|
||||
|
||||
const corsOptions = <span class="cstat-no" title="statement not covered" >{</span>
|
||||
origin: <span class="fstat-no" title="function not covered" >fu</span>nction (origin, callback) {
|
||||
// TODO 1: Ha nincs origin (pl. Postman, curl, backend-backend hívás), engedélyezd
|
||||
// Tipp: if (!origin) return callback(null, true);
|
||||
|
||||
// TODO 2: Ha az origin benne van az allowedOrigins-ban, engedélyezd
|
||||
// Tipp: if (allowedOrigins.includes(origin)) return callback(null, true);
|
||||
|
||||
// TODO 3: Egyébként tiltsd le CORS hibával
|
||||
// Tipp: callback(new Error('Not allowed by CORS'));
|
||||
},
|
||||
credentials: true, // Cookie/Auth header engedélyezése
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization']
|
||||
};
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = cors(corsOptions);</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/middlewares</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> api/middlewares</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/17</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/17</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="authMiddleware.js"><a href="authMiddleware.js.html">authMiddleware.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="11" class="abs low">0/11</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="11" class="abs low">0/11</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="corsMiddleware.js"><a href="corsMiddleware.js.html">corsMiddleware.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="scopeMiddleware.js"><a href="scopeMiddleware.js.html">scopeMiddleware.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/middlewares/scopeMiddleware.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/middlewares</a> scopeMiddleware.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Request szintű DI scope létrehozása
|
||||
* Middleware ami minden kéréshez új DI scope-ot készít
|
||||
*/
|
||||
function <span class="fstat-no" title="function not covered" >scopeMiddleware(</span>container) {
|
||||
<span class="cstat-no" title="statement not covered" > return <span class="fstat-no" title="function not covered" >(r</span>eq, res, next) => {</span>
|
||||
// TODO 1: Hozz létre request-specifikus scope-ot
|
||||
// Tipp: const scope = container.createScope();
|
||||
|
||||
// TODO 2: Tárold el a scope-ot req.scope alatt
|
||||
// Tipp: req.scope = scope;
|
||||
|
||||
// TODO 3: Hívd meg a next()-et
|
||||
// Tipp: next();
|
||||
};
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = scopeMiddleware;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/routers/authRoutes.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/routers</a> authRoutes.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/13</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/5</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/9</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const express = <span class="cstat-no" title="statement not covered" >require('express');</span>
|
||||
|
||||
/**
|
||||
* Auth Routes
|
||||
* Public endpoints (nincs JWT védelem)
|
||||
*
|
||||
* @param {Container} container - DI Container
|
||||
*/
|
||||
function <span class="fstat-no" title="function not covered" >createAuthRoutes(</span>container) {
|
||||
const router = <span class="cstat-no" title="statement not covered" >express.Router();</span>
|
||||
|
||||
// AuthController lekérése a DI Container-ből
|
||||
const authController = <span class="cstat-no" title="statement not covered" >container.resolve('AuthController');</span>
|
||||
|
||||
/**
|
||||
* POST /api/auth/register - User regisztráció
|
||||
* Body: { name, email, password }
|
||||
* Response: { user, token }
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.post('/register', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >authController.register(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* POST /api/auth/login - User bejelentkezés
|
||||
* Body: { email, password }
|
||||
* Response: { user, token }
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.post('/login', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >authController.login(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* POST /api/auth/logout - User kijelentkezés
|
||||
* Clears the authentication cookie
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.post('/logout', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >authController.logout(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* OPTIONS /api/auth/* - CORS preflight
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.options('*', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >res.sendStatus(204))</span>;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return router;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = createAuthRoutes;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/routers</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> api/routers</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/29</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/20</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="authRoutes.js"><a href="authRoutes.js.html">authRoutes.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="13" class="abs low">0/13</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="9" class="abs low">0/9</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="userRoutes.js"><a href="userRoutes.js.html">userRoutes.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="16" class="abs low">0/16</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="6" class="abs low">0/6</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="11" class="abs low">0/11</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/routers/userRoutes.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/routers</a> userRoutes.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/16</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/6</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const express = <span class="cstat-no" title="statement not covered" >require('express');</span>
|
||||
const authMiddleware = <span class="cstat-no" title="statement not covered" >require('../middlewares/authMiddleware');</span>
|
||||
|
||||
/**
|
||||
* User Routes
|
||||
* Protected endpoints (JWT authentication required)
|
||||
*
|
||||
* @param {Container} container - DI Container
|
||||
*/
|
||||
function <span class="fstat-no" title="function not covered" >createUserRoutes(</span>container) {
|
||||
const router = <span class="cstat-no" title="statement not covered" >express.Router();</span>
|
||||
|
||||
// UserController lekérése a DI Container-ből
|
||||
const userController = <span class="cstat-no" title="statement not covered" >container.resolve('UserController');</span>
|
||||
|
||||
/**
|
||||
* GET /api/users/me - Bejelentkezett user adatai
|
||||
* Headers: Authorization: Bearer <token>
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.get('/me', authMiddleware, <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >userController.getMe(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* PUT /api/users/me - User profil frissítése
|
||||
* Headers: Authorization: Bearer <token>
|
||||
* Body: { name }
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.put('/me', authMiddleware, <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >userController.updateMe(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* GET /api/users - Összes user lekérése (protected)
|
||||
* Headers: Authorization: Bearer <token>
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.get('/', authMiddleware, <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >userController.getAll(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* GET /api/users/:id - User lekérése ID alapján (protected)
|
||||
* Headers: Authorization: Bearer <token>
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.get('/:id', authMiddleware, <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >userController.getById(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* OPTIONS /api/users/* - CORS preflight
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.options('*', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >res.sendStatus(204))</span>;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return router;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = createUserRoutes;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/auth/commands/LoginUserCommand.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/auth/commands</a> LoginUserCommand.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Login User Command
|
||||
* Command object for user login
|
||||
*/
|
||||
class LoginUserCommand {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(email, password) {
|
||||
<span class="cstat-no" title="statement not covered" > this.email = email;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.password = password;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = LoginUserCommand;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/auth/commands/LoginUserCommandHandler.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/auth/commands</a> LoginUserCommandHandler.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/17</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/17</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const bcrypt = <span class="cstat-no" title="statement not covered" >require('bcryptjs');</span>
|
||||
const JwtService = <span class="cstat-no" title="statement not covered" >require('../../services/JwtService');</span>
|
||||
|
||||
const jwtService = <span class="cstat-no" title="statement not covered" >new JwtService();</span>
|
||||
|
||||
/**
|
||||
* Login User Command Handler
|
||||
* Handles user login authentication
|
||||
*/
|
||||
class LoginUserCommandHandler {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(prisma) {
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = prisma;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute login command
|
||||
* @param {LoginUserCommand} command
|
||||
* @returns {Promise<Object>} { user, token }
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync handle(command) {
|
||||
const { email, password } = <span class="cstat-no" title="statement not covered" >command;</span>
|
||||
|
||||
// Validáció
|
||||
<span class="cstat-no" title="statement not covered" > if (!email || !password) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Email and password are required');</span>
|
||||
}
|
||||
|
||||
// User keresése email alapján
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.prisma.user.findUnique({</span>
|
||||
where: { email }
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!user) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Invalid email or password');</span>
|
||||
}
|
||||
|
||||
// Jelszó ellenőrzése
|
||||
const isPasswordValid = <span class="cstat-no" title="statement not covered" >await bcrypt.compare(password, user.password);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!isPasswordValid) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Invalid email or password');</span>
|
||||
}
|
||||
|
||||
// JWT token generálása
|
||||
const token = <span class="cstat-no" title="statement not covered" >jwtService.generateToken({</span>
|
||||
userId: user.id,
|
||||
email: user.email
|
||||
});
|
||||
|
||||
// Jelszót ne adjuk vissza
|
||||
const { password: _, ...userWithoutPassword } = <span class="cstat-no" title="statement not covered" >user;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return {</span>
|
||||
user: userWithoutPassword,
|
||||
token
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = LoginUserCommandHandler;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/auth/commands/RegisterUserCommand.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/auth/commands</a> RegisterUserCommand.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Register User Command
|
||||
* Command object for user registration
|
||||
*/
|
||||
class RegisterUserCommand {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(name, email, password) {
|
||||
<span class="cstat-no" title="statement not covered" > this.name = name;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.email = email;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.password = password;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = RegisterUserCommand;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/auth/commands/RegisterUserCommandHandler.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/auth/commands</a> RegisterUserCommandHandler.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/13</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a>
|
||||
<a name='L78'></a><a href='#L78'>78</a>
|
||||
<a name='L79'></a><a href='#L79'>79</a>
|
||||
<a name='L80'></a><a href='#L80'>80</a>
|
||||
<a name='L81'></a><a href='#L81'>81</a>
|
||||
<a name='L82'></a><a href='#L82'>82</a>
|
||||
<a name='L83'></a><a href='#L83'>83</a>
|
||||
<a name='L84'></a><a href='#L84'>84</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const bcrypt = <span class="cstat-no" title="statement not covered" >require('bcryptjs');</span>
|
||||
const JwtService = <span class="cstat-no" title="statement not covered" >require('../../services/JwtService');</span>
|
||||
|
||||
const jwtService = <span class="cstat-no" title="statement not covered" >new JwtService();</span>
|
||||
|
||||
/**
|
||||
* Register User Command Handler
|
||||
* Handles user registration business logic
|
||||
*/
|
||||
class RegisterUserCommandHandler {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(prisma, emailService) {
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = prisma;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.emailService = emailService;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute user registration command
|
||||
* @param {RegisterUserCommand} command
|
||||
* @returns {Promise<Object>} { user, token }
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync handle(command) {
|
||||
const { name, email, password } = <span class="cstat-no" title="statement not covered" >command;</span>
|
||||
|
||||
// Validáció
|
||||
<span class="cstat-no" title="statement not covered" > if (!name || !email || !password) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Name, email and password are required');</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (password.length < 6) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Password must be at least 6 characters long');</span>
|
||||
}
|
||||
|
||||
// Email formátum ellenőrzés
|
||||
const emailRegex = <span class="cstat-no" title="statement not covered" >/^[^\s@]+@[^\s@]+\.[^\s@]+$/;</span>
|
||||
<span class="cstat-no" title="statement not covered" > if (!emailRegex.test(email)) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Invalid email format');</span>
|
||||
}
|
||||
|
||||
// Ellenőrizzük, hogy létezik-e már a user
|
||||
const existingUser = <span class="cstat-no" title="statement not covered" >await this.prisma.user.findUnique({</span>
|
||||
where: { email }
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (existingUser) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('User with this email already exists');</span>
|
||||
}
|
||||
|
||||
// Jelszó hashelése (bcrypt)
|
||||
const hashedPassword = <span class="cstat-no" title="statement not covered" >await bcrypt.hash(password, 10);</span>
|
||||
|
||||
// User létrehozása
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.prisma.user.create({</span>
|
||||
data: {
|
||||
name,
|
||||
email,
|
||||
password: hashedPassword
|
||||
}
|
||||
});
|
||||
|
||||
// Welcome email küldése (async, nem várunk rá)
|
||||
<span class="cstat-no" title="statement not covered" > if (this.emailService) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.emailService.sendWelcomeEmail(email, name).catch(<span class="fstat-no" title="function not covered" >er</span>r => {</span>
|
||||
<span class="cstat-no" title="statement not covered" > console.error('❌ Failed to send welcome email:', err.message);</span>
|
||||
});
|
||||
}
|
||||
|
||||
// JWT token generálása
|
||||
const token = <span class="cstat-no" title="statement not covered" >jwtService.generateToken({</span>
|
||||
userId: user.id,
|
||||
email: user.email
|
||||
});
|
||||
|
||||
// Jelszót ne adjuk vissza a response-ban
|
||||
const { password: _, ...userWithoutPassword } = <span class="cstat-no" title="statement not covered" >user;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return {</span>
|
||||
user: userWithoutPassword,
|
||||
token
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = RegisterUserCommandHandler;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/auth/commands</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> application/auth/commands</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/49</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/21</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/7</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/49</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="LoginUserCommand.js"><a href="LoginUserCommand.js.html">LoginUserCommand.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="3" class="abs low">0/3</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="3" class="abs low">0/3</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="LoginUserCommandHandler.js"><a href="LoginUserCommandHandler.js.html">LoginUserCommandHandler.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="17" class="abs low">0/17</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="17" class="abs low">0/17</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="RegisterUserCommand.js"><a href="RegisterUserCommand.js.html">RegisterUserCommand.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="RegisterUserCommandHandler.js"><a href="RegisterUserCommandHandler.js.html">RegisterUserCommandHandler.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="13" class="abs low">0/13</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="3" class="abs low">0/3</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/services/Container.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">application/services</a> Container.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>2/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>4/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>1/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Dependency Injection Container
|
||||
* Supports singleton, transient, and scoped lifetimes
|
||||
*/
|
||||
class Container {
|
||||
constructor() {
|
||||
// TODO 1: Inicializáld a services Map-et (singleton instance-ok tárolása)
|
||||
// TODO 2: Inicializáld a factories Map-et (factory függvények tárolása)
|
||||
// TODO 3: Inicializáld a lifetimes Map-et (lifecycle típusok tárolása)
|
||||
|
||||
// Példa inicializálás:
|
||||
// this.services = new Map();
|
||||
// this.factories = new Map();
|
||||
// this.lifetimes = new Map();
|
||||
}
|
||||
|
||||
register(name, factory, lifetime = 'singleton') {
|
||||
// TODO 4: Tárold el a factory függvényt (this.factories.set(name, factory))
|
||||
// TODO 5: Tárold el a lifetime típust (this.lifetimes.set(name, lifetime))
|
||||
// TODO 6: Ha a lifetime === 'singleton', azonnal példányosítsd:
|
||||
// - Hívd meg a factory-t: const instance = factory();
|
||||
// - Tárold el: this.services.set(name, instance);
|
||||
}
|
||||
|
||||
resolve(name, scope = null) {
|
||||
// TODO 7: Ha a service regisztrálva van mint 'scoped' ÉS van scope paraméter:
|
||||
// - Ellenőrizd: if (scope && scope.has(name)) return scope.get(name);
|
||||
// - Ha nincs még a scope-ban, példányosítsd és tárold:
|
||||
// const instance = this.factories.get(name)();
|
||||
// scope.set(name, instance);
|
||||
// return instance;
|
||||
|
||||
// TODO 8: Ha singleton, add vissza a services-ből:
|
||||
// if (this.lifetimes.get(name) === 'singleton') {
|
||||
// return this.services.get(name);
|
||||
// }
|
||||
|
||||
// TODO 9: Ha transient, minden alkalommal hívj egy új factory-t:
|
||||
// if (this.lifetimes.get(name) === 'transient') {
|
||||
// return this.factories.get(name)();
|
||||
// }
|
||||
|
||||
// TODO 10: Ha nem regisztrált a service, dobj hibát:
|
||||
// throw new Error(`Service '${name}' is not registered`);
|
||||
}
|
||||
|
||||
createScope() {
|
||||
// TODO 11: Hozz létre egy új Map-et az scoped instance-oknak
|
||||
// TODO 12: Térj vissza egy objektummal ami tartalmaz egy resolve metódust:
|
||||
// const scopeMap = new Map();
|
||||
// return {
|
||||
// resolve: (name) => this.resolve(name, scopeMap)
|
||||
// };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Container;
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/services/EmailService.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">application/services</a> EmailService.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Email Service
|
||||
* Nodemailer + Handlebars template based email sending
|
||||
*
|
||||
* TODO: Implement by students
|
||||
*/
|
||||
class EmailService {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor() {
|
||||
// TODO 1: Hozz létre Nodemailer transportot Ethereal tesztelő SMTP-vel
|
||||
// this.transporter = nodemailer.createTransport({
|
||||
// host: 'smtp.ethereal.email',
|
||||
// port: 587,
|
||||
// secure: false, // TLS
|
||||
// auth: {
|
||||
// user: process.env.ETHEREAL_USER || 'your-test-email@ethereal.email',
|
||||
// pass: process.env.ETHEREAL_PASS || 'your-test-password'
|
||||
// }
|
||||
// });
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > console.log('📧 EmailService initialized');</span>
|
||||
}
|
||||
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync sendWelcomeEmail(userEmail, userName) {
|
||||
// TODO 2-6: Implement email sending with Handlebars template
|
||||
// For now, just log to console
|
||||
<span class="cstat-no" title="statement not covered" > console.log(`📧 Would send welcome email to ${userEmail} (${userName})`);</span>
|
||||
<span class="cstat-no" title="statement not covered" > return true;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = EmailService;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+427
@@ -0,0 +1,427 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/services/JwtService.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">application/services</a> JwtService.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/29</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/20</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/29</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a>
|
||||
<a name='L78'></a><a href='#L78'>78</a>
|
||||
<a name='L79'></a><a href='#L79'>79</a>
|
||||
<a name='L80'></a><a href='#L80'>80</a>
|
||||
<a name='L81'></a><a href='#L81'>81</a>
|
||||
<a name='L82'></a><a href='#L82'>82</a>
|
||||
<a name='L83'></a><a href='#L83'>83</a>
|
||||
<a name='L84'></a><a href='#L84'>84</a>
|
||||
<a name='L85'></a><a href='#L85'>85</a>
|
||||
<a name='L86'></a><a href='#L86'>86</a>
|
||||
<a name='L87'></a><a href='#L87'>87</a>
|
||||
<a name='L88'></a><a href='#L88'>88</a>
|
||||
<a name='L89'></a><a href='#L89'>89</a>
|
||||
<a name='L90'></a><a href='#L90'>90</a>
|
||||
<a name='L91'></a><a href='#L91'>91</a>
|
||||
<a name='L92'></a><a href='#L92'>92</a>
|
||||
<a name='L93'></a><a href='#L93'>93</a>
|
||||
<a name='L94'></a><a href='#L94'>94</a>
|
||||
<a name='L95'></a><a href='#L95'>95</a>
|
||||
<a name='L96'></a><a href='#L96'>96</a>
|
||||
<a name='L97'></a><a href='#L97'>97</a>
|
||||
<a name='L98'></a><a href='#L98'>98</a>
|
||||
<a name='L99'></a><a href='#L99'>99</a>
|
||||
<a name='L100'></a><a href='#L100'>100</a>
|
||||
<a name='L101'></a><a href='#L101'>101</a>
|
||||
<a name='L102'></a><a href='#L102'>102</a>
|
||||
<a name='L103'></a><a href='#L103'>103</a>
|
||||
<a name='L104'></a><a href='#L104'>104</a>
|
||||
<a name='L105'></a><a href='#L105'>105</a>
|
||||
<a name='L106'></a><a href='#L106'>106</a>
|
||||
<a name='L107'></a><a href='#L107'>107</a>
|
||||
<a name='L108'></a><a href='#L108'>108</a>
|
||||
<a name='L109'></a><a href='#L109'>109</a>
|
||||
<a name='L110'></a><a href='#L110'>110</a>
|
||||
<a name='L111'></a><a href='#L111'>111</a>
|
||||
<a name='L112'></a><a href='#L112'>112</a>
|
||||
<a name='L113'></a><a href='#L113'>113</a>
|
||||
<a name='L114'></a><a href='#L114'>114</a>
|
||||
<a name='L115'></a><a href='#L115'>115</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const jwt = <span class="cstat-no" title="statement not covered" >require('jsonwebtoken');</span>
|
||||
|
||||
/**
|
||||
* JWT Service
|
||||
* Handles JWT token generation, verification, and cookie management
|
||||
*/
|
||||
class JwtService {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor() {
|
||||
<span class="cstat-no" title="statement not covered" > this.secret = process.env.JWT_SECRET || 'default-secret-change-me';</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.expiresIn = process.env.JWT_EXPIRES_IN || '1h';</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.cookieName = 'auth_token';</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JWT token
|
||||
* @param {Object} payload - { userId, email }
|
||||
* @returns {string} JWT token
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > ge</span>nerateToken(payload) {
|
||||
<span class="cstat-no" title="statement not covered" > return jwt.sign(payload, this.secret, { expiresIn: this.expiresIn });</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify JWT token
|
||||
* @param {string} token - JWT token
|
||||
* @returns {Object} Decoded payload
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > ve</span>rifyToken(token) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return jwt.verify(token, this.secret);</span>
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Invalid or expired token');</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract token from Authorization header (Bearer token)
|
||||
* @param {string} authHeader - Authorization header value
|
||||
* @returns {string|null} Token or null
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > ex</span>tractTokenFromHeader(authHeader) {
|
||||
<span class="cstat-no" title="statement not covered" > if (!authHeader) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return null;</span>
|
||||
}
|
||||
|
||||
const parts = <span class="cstat-no" title="statement not covered" >authHeader.split(' ');</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (parts.length !== 2 || parts[0] !== 'Bearer') {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return null;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return parts[1];</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract token from cookies
|
||||
* @param {Object} cookies - Request cookies object
|
||||
* @returns {string|null} Token or null
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > ex</span>tractTokenFromCookies(cookies) {
|
||||
<span class="cstat-no" title="statement not covered" > if (!cookies || !cookies[this.cookieName]) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return null;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return cookies[this.cookieName];</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookie options for setting JWT cookie
|
||||
* @returns {Object} Cookie options
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > ge</span>tCookieOptions() {
|
||||
const isProduction = <span class="cstat-no" title="statement not covered" >process.env.NODE_ENV === 'production';</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return {</span>
|
||||
httpOnly: true, // Prevents XSS attacks
|
||||
secure: isProduction, // HTTPS only in production
|
||||
sameSite: 'strict', // CSRF protection
|
||||
maxAge: this._getMaxAge(),
|
||||
path: '/'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookie max age in milliseconds
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > _g</span>etMaxAge() {
|
||||
// Parse JWT_EXPIRES_IN (e.g., "1h", "7d")
|
||||
const expiresIn = <span class="cstat-no" title="statement not covered" >this.expiresIn;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (expiresIn.endsWith('h')) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return parseInt(expiresIn) * 60 * 60 * 1000;</span>
|
||||
} else <span class="cstat-no" title="statement not covered" >if (expiresIn.endsWith('d')) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return parseInt(expiresIn) * 24 * 60 * 60 * 1000;</span>
|
||||
} else <span class="cstat-no" title="statement not covered" >if (expiresIn.endsWith('m')) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return parseInt(expiresIn) * 60 * 1000;</span>
|
||||
}
|
||||
|
||||
// Default: 1 hour
|
||||
<span class="cstat-no" title="statement not covered" > return 60 * 60 * 1000;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookie name
|
||||
* @returns {string}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > ge</span>tCookieName() {
|
||||
<span class="cstat-no" title="statement not covered" > return this.cookieName;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = JwtService;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/services</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> application/services</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">2.94% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>1/34</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">9.09% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>2/22</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">28.57% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>4/14</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">2.94% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>1/34</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="Container.js"><a href="Container.js.html">Container.js</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="2" class="abs high">2/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="4" class="abs high">4/4</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="EmailService.js"><a href="EmailService.js.html">EmailService.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="JwtService.js"><a href="JwtService.js.html">JwtService.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="29" class="abs low">0/29</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="20" class="abs low">0/20</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="29" class="abs low">0/29</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/commands/UpdateUserProfileCommand.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/user/commands</a> UpdateUserProfileCommand.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Update User Profile Command
|
||||
* Command object for updating user profile
|
||||
*/
|
||||
class UpdateUserProfileCommand {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(userId, name) {
|
||||
<span class="cstat-no" title="statement not covered" > this.userId = userId;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.name = name;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = UpdateUserProfileCommand;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/commands/UpdateUserProfileCommandHandler.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/user/commands</a> UpdateUserProfileCommandHandler.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Update User Profile Command Handler
|
||||
* Handles user profile update logic
|
||||
*/
|
||||
class UpdateUserProfileCommandHandler {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(prisma) {
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = prisma;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute update profile command
|
||||
* @param {UpdateUserProfileCommand} command
|
||||
* @returns {Promise<Object>} Updated user data
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync handle(command) {
|
||||
const { userId, name } = <span class="cstat-no" title="statement not covered" >command;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!name) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Name is required');</span>
|
||||
}
|
||||
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.prisma.user.update({</span>
|
||||
where: { id: userId },
|
||||
data: { name }
|
||||
});
|
||||
|
||||
const { password, ...userWithoutPassword } = <span class="cstat-no" title="statement not covered" >user;</span>
|
||||
<span class="cstat-no" title="statement not covered" > return userWithoutPassword;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = UpdateUserProfileCommandHandler;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/commands</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> application/user/commands</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="UpdateUserProfileCommand.js"><a href="UpdateUserProfileCommand.js.html">UpdateUserProfileCommand.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="3" class="abs low">0/3</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="3" class="abs low">0/3</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="UpdateUserProfileCommandHandler.js"><a href="UpdateUserProfileCommandHandler.js.html">UpdateUserProfileCommandHandler.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/queries/GetAllUsersQuery.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/user/queries</a> GetAllUsersQuery.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Get All Users Query
|
||||
* Query object for retrieving all users
|
||||
*/
|
||||
class GetAllUsersQuery {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor() {
|
||||
// No parameters needed for getting all users
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = GetAllUsersQuery;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/queries/GetAllUsersQueryHandler.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/user/queries</a> GetAllUsersQueryHandler.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/5</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Get All Users Query Handler
|
||||
* Handles retrieval of all users
|
||||
*/
|
||||
class GetAllUsersQueryHandler {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(prisma) {
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = prisma;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute get all users query
|
||||
* @param {GetAllUsersQuery} query
|
||||
* @returns {Promise<Array>} List of users without passwords
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync handle(query) {
|
||||
const users = <span class="cstat-no" title="statement not covered" >await this.prisma.user.findMany({</span>
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
// Remove passwords from all users
|
||||
<span class="cstat-no" title="statement not covered" > return users.map(<span class="fstat-no" title="function not covered" >({</span> password, ...user }) => <span class="cstat-no" title="statement not covered" >user)</span>;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = GetAllUsersQueryHandler;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/queries/GetMeQuery.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/user/queries</a> GetMeQuery.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Get Me Query
|
||||
* Query object for getting current authenticated user
|
||||
*/
|
||||
class GetMeQuery {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(userId) {
|
||||
<span class="cstat-no" title="statement not covered" > this.userId = userId;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = GetMeQuery;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/queries/GetMeQueryHandler.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/user/queries</a> GetMeQueryHandler.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Get Me Query Handler
|
||||
* Handles retrieval of current authenticated user
|
||||
*/
|
||||
class GetMeQueryHandler {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(prisma) {
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = prisma;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute get me query
|
||||
* @param {GetMeQuery} query
|
||||
* @returns {Promise<Object>} User data without password
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync handle(query) {
|
||||
const { userId } = <span class="cstat-no" title="statement not covered" >query;</span>
|
||||
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.prisma.user.findUnique({</span>
|
||||
where: { id: userId }
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!user) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('User not found');</span>
|
||||
}
|
||||
|
||||
const { password, ...userWithoutPassword } = <span class="cstat-no" title="statement not covered" >user;</span>
|
||||
<span class="cstat-no" title="statement not covered" > return userWithoutPassword;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = GetMeQueryHandler;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/queries/GetUserByIdQuery.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/user/queries</a> GetUserByIdQuery.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Get User By ID Query
|
||||
* Query object for retrieving a user by ID
|
||||
*/
|
||||
class GetUserByIdQuery {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(userId) {
|
||||
<span class="cstat-no" title="statement not covered" > this.userId = userId;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = GetUserByIdQuery;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/queries/GetUserByIdQueryHandler.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/user/queries</a> GetUserByIdQueryHandler.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/10</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/6</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/10</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Get User By ID Query Handler
|
||||
* Handles retrieval of a specific user by ID
|
||||
*/
|
||||
class GetUserByIdQueryHandler {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(prisma) {
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = prisma;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute get user by ID query
|
||||
* @param {GetUserByIdQuery} query
|
||||
* @returns {Promise<Object>} User data without password
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync handle(query) {
|
||||
const { userId } = <span class="cstat-no" title="statement not covered" >query;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!userId || isNaN(userId)) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Valid user ID is required');</span>
|
||||
}
|
||||
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.prisma.user.findUnique({</span>
|
||||
where: { id: parseInt(userId) }
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!user) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('User not found');</span>
|
||||
}
|
||||
|
||||
const { password, ...userWithoutPassword } = <span class="cstat-no" title="statement not covered" >user;</span>
|
||||
<span class="cstat-no" title="statement not covered" > return userWithoutPassword;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = GetUserByIdQueryHandler;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/user/queries</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> application/user/queries</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/28</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/10</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/27</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="GetAllUsersQuery.js"><a href="GetAllUsersQuery.js.html">GetAllUsersQuery.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="GetAllUsersQueryHandler.js"><a href="GetAllUsersQueryHandler.js.html">GetAllUsersQueryHandler.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="3" class="abs low">0/3</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="GetMeQuery.js"><a href="GetMeQuery.js.html">GetMeQuery.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="GetMeQueryHandler.js"><a href="GetMeQueryHandler.js.html">GetMeQueryHandler.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="GetUserByIdQuery.js"><a href="GetUserByIdQuery.js.html">GetUserByIdQuery.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="GetUserByIdQueryHandler.js"><a href="GetUserByIdQueryHandler.js.html">GetUserByIdQueryHandler.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="10" class="abs low">0/10</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="6" class="abs low">0/6</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="10" class="abs low">0/10</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
body, html {
|
||||
margin:0; padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
font-family: Helvetica Neue, Helvetica, Arial;
|
||||
font-size: 14px;
|
||||
color:#333;
|
||||
}
|
||||
.small { font-size: 12px; }
|
||||
*, *:after, *:before {
|
||||
-webkit-box-sizing:border-box;
|
||||
-moz-box-sizing:border-box;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
h1 { font-size: 20px; margin: 0;}
|
||||
h2 { font-size: 14px; }
|
||||
pre {
|
||||
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-tab-size: 2;
|
||||
-o-tab-size: 2;
|
||||
tab-size: 2;
|
||||
}
|
||||
a { color:#0074D9; text-decoration:none; }
|
||||
a:hover { text-decoration:underline; }
|
||||
.strong { font-weight: bold; }
|
||||
.space-top1 { padding: 10px 0 0 0; }
|
||||
.pad2y { padding: 20px 0; }
|
||||
.pad1y { padding: 10px 0; }
|
||||
.pad2x { padding: 0 20px; }
|
||||
.pad2 { padding: 20px; }
|
||||
.pad1 { padding: 10px; }
|
||||
.space-left2 { padding-left:55px; }
|
||||
.space-right2 { padding-right:20px; }
|
||||
.center { text-align:center; }
|
||||
.clearfix { display:block; }
|
||||
.clearfix:after {
|
||||
content:'';
|
||||
display:block;
|
||||
height:0;
|
||||
clear:both;
|
||||
visibility:hidden;
|
||||
}
|
||||
.fl { float: left; }
|
||||
@media only screen and (max-width:640px) {
|
||||
.col3 { width:100%; max-width:100%; }
|
||||
.hide-mobile { display:none!important; }
|
||||
}
|
||||
|
||||
.quiet {
|
||||
color: #7f7f7f;
|
||||
color: rgba(0,0,0,0.5);
|
||||
}
|
||||
.quiet a { opacity: 0.7; }
|
||||
|
||||
.fraction {
|
||||
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||
font-size: 10px;
|
||||
color: #555;
|
||||
background: #E8E8E8;
|
||||
padding: 4px 5px;
|
||||
border-radius: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.path a:link, div.path a:visited { color: #333; }
|
||||
table.coverage {
|
||||
border-collapse: collapse;
|
||||
margin: 10px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.coverage td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.coverage td.line-count {
|
||||
text-align: right;
|
||||
padding: 0 5px 0 20px;
|
||||
}
|
||||
table.coverage td.line-coverage {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
min-width:20px;
|
||||
}
|
||||
|
||||
table.coverage td span.cline-any {
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
width: 100%;
|
||||
}
|
||||
.missing-if-branch {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
padding: 0 4px;
|
||||
background: #333;
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.skip-if-branch {
|
||||
display: none;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
padding: 0 4px;
|
||||
background: #ccc;
|
||||
color: white;
|
||||
}
|
||||
.missing-if-branch .typ, .skip-if-branch .typ {
|
||||
color: inherit !important;
|
||||
}
|
||||
.coverage-summary {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
.coverage-summary tr { border-bottom: 1px solid #bbb; }
|
||||
.keyline-all { border: 1px solid #ddd; }
|
||||
.coverage-summary td, .coverage-summary th { padding: 10px; }
|
||||
.coverage-summary tbody { border: 1px solid #bbb; }
|
||||
.coverage-summary td { border-right: 1px solid #bbb; }
|
||||
.coverage-summary td:last-child { border-right: none; }
|
||||
.coverage-summary th {
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.coverage-summary th.file { border-right: none !important; }
|
||||
.coverage-summary th.pct { }
|
||||
.coverage-summary th.pic,
|
||||
.coverage-summary th.abs,
|
||||
.coverage-summary td.pct,
|
||||
.coverage-summary td.abs { text-align: right; }
|
||||
.coverage-summary td.file { white-space: nowrap; }
|
||||
.coverage-summary td.pic { min-width: 120px !important; }
|
||||
.coverage-summary tfoot td { }
|
||||
|
||||
.coverage-summary .sorter {
|
||||
height: 10px;
|
||||
width: 7px;
|
||||
display: inline-block;
|
||||
margin-left: 0.5em;
|
||||
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
|
||||
}
|
||||
.coverage-summary .sorted .sorter {
|
||||
background-position: 0 -20px;
|
||||
}
|
||||
.coverage-summary .sorted-desc .sorter {
|
||||
background-position: 0 -10px;
|
||||
}
|
||||
.status-line { height: 10px; }
|
||||
/* yellow */
|
||||
.cbranch-no { background: yellow !important; color: #111; }
|
||||
/* dark red */
|
||||
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
|
||||
.low .chart { border:1px solid #C21F39 }
|
||||
.highlighted,
|
||||
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
|
||||
background: #C21F39 !important;
|
||||
}
|
||||
/* medium red */
|
||||
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
|
||||
/* light red */
|
||||
.low, .cline-no { background:#FCE1E5 }
|
||||
/* light green */
|
||||
.high, .cline-yes { background:rgb(230,245,208) }
|
||||
/* medium green */
|
||||
.cstat-yes { background:rgb(161,215,106) }
|
||||
/* dark green */
|
||||
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
|
||||
.high .chart { border:1px solid rgb(77,146,33) }
|
||||
/* dark yellow (gold) */
|
||||
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
|
||||
.medium .chart { border:1px solid #f9cd0b; }
|
||||
/* light yellow */
|
||||
.medium { background: #fff4c2; }
|
||||
|
||||
.cstat-skip { background: #ddd; color: #111; }
|
||||
.fstat-skip { background: #ddd; color: #111 !important; }
|
||||
.cbranch-skip { background: #ddd !important; color: #111; }
|
||||
|
||||
span.cline-neutral { background: #eaeaea; }
|
||||
|
||||
.coverage-summary td.empty {
|
||||
opacity: .5;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
line-height: 1;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.cover-fill, .cover-empty {
|
||||
display:inline-block;
|
||||
height: 12px;
|
||||
}
|
||||
.chart {
|
||||
line-height: 0;
|
||||
}
|
||||
.cover-empty {
|
||||
background: white;
|
||||
}
|
||||
.cover-full {
|
||||
border-right: none !important;
|
||||
}
|
||||
pre.prettyprint {
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.com { color: #999 !important; }
|
||||
.ignore-none { color: #999; font-weight: normal; }
|
||||
|
||||
.wrapper {
|
||||
min-height: 100%;
|
||||
height: auto !important;
|
||||
height: 100%;
|
||||
margin: 0 auto -48px;
|
||||
}
|
||||
.footer, .push {
|
||||
height: 48px;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable */
|
||||
var jumpToCode = (function init() {
|
||||
// Classes of code we would like to highlight in the file view
|
||||
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
|
||||
|
||||
// Elements to highlight in the file listing view
|
||||
var fileListingElements = ['td.pct.low'];
|
||||
|
||||
// We don't want to select elements that are direct descendants of another match
|
||||
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
|
||||
|
||||
// Selector that finds elements on the page to which we can jump
|
||||
var selector =
|
||||
fileListingElements.join(', ') +
|
||||
', ' +
|
||||
notSelector +
|
||||
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
|
||||
|
||||
// The NodeList of matching elements
|
||||
var missingCoverageElements = document.querySelectorAll(selector);
|
||||
|
||||
var currentIndex;
|
||||
|
||||
function toggleClass(index) {
|
||||
missingCoverageElements
|
||||
.item(currentIndex)
|
||||
.classList.remove('highlighted');
|
||||
missingCoverageElements.item(index).classList.add('highlighted');
|
||||
}
|
||||
|
||||
function makeCurrent(index) {
|
||||
toggleClass(index);
|
||||
currentIndex = index;
|
||||
missingCoverageElements.item(index).scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'center'
|
||||
});
|
||||
}
|
||||
|
||||
function goToPrevious() {
|
||||
var nextIndex = 0;
|
||||
if (typeof currentIndex !== 'number' || currentIndex === 0) {
|
||||
nextIndex = missingCoverageElements.length - 1;
|
||||
} else if (missingCoverageElements.length > 1) {
|
||||
nextIndex = currentIndex - 1;
|
||||
}
|
||||
|
||||
makeCurrent(nextIndex);
|
||||
}
|
||||
|
||||
function goToNext() {
|
||||
var nextIndex = 0;
|
||||
|
||||
if (
|
||||
typeof currentIndex === 'number' &&
|
||||
currentIndex < missingCoverageElements.length - 1
|
||||
) {
|
||||
nextIndex = currentIndex + 1;
|
||||
}
|
||||
|
||||
makeCurrent(nextIndex);
|
||||
}
|
||||
|
||||
return function jump(event) {
|
||||
if (
|
||||
document.getElementById('fileSearch') === document.activeElement &&
|
||||
document.activeElement != null
|
||||
) {
|
||||
// if we're currently focused on the search input, we don't want to navigate
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.which) {
|
||||
case 78: // n
|
||||
case 74: // j
|
||||
goToNext();
|
||||
break;
|
||||
case 66: // b
|
||||
case 75: // k
|
||||
case 80: // p
|
||||
goToPrevious();
|
||||
break;
|
||||
}
|
||||
};
|
||||
})();
|
||||
window.addEventListener('keydown', jumpToCode);
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for domain/irepositories/IUserRepository.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">domain/irepositories</a> IUserRepository.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/7</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/6</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/7</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* User Repository Interface
|
||||
* Defines contract for user data access
|
||||
*/
|
||||
class IUserRepository {
|
||||
/**
|
||||
* Find user by ID
|
||||
* @param {number} id
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync findById(id) {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Method findById() must be implemented');</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by email
|
||||
* @param {string} email
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync findByEmail(email) {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Method findByEmail() must be implemented');</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all users
|
||||
* @returns {Promise<User[]>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync findAll() {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Method findAll() must be implemented');</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new user
|
||||
* @param {User} user
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync create(user) {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Method create() must be implemented');</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing user
|
||||
* @param {User} user
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync update(user) {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Method update() must be implemented');</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user by ID
|
||||
* @param {number} id
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync delete(id) {
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Method delete() must be implemented');</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = IUserRepository;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for domain/irepositories</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> domain/irepositories</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/7</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/6</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/7</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="IUserRepository.js"><a href="IUserRepository.js.html">IUserRepository.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="7" class="abs low">0/7</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="6" class="abs low">0/6</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="7" class="abs low">0/7</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for domain/models/User.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">domain/models</a> User.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/22</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/16</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/5</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/22</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* User Domain Model
|
||||
* Pure domain entity with business logic validation
|
||||
*/
|
||||
class User {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(id, name, email, password, createdAt, updatedAt) {
|
||||
<span class="cstat-no" title="statement not covered" > this.id = id;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.name = name;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.email = email;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.password = password;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.createdAt = createdAt;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.updatedAt = updatedAt;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new User
|
||||
* @param {Object} data - { name, email, password }
|
||||
* @returns {User}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > st</span>atic create(data) {
|
||||
const { name, email, password } = <span class="cstat-no" title="statement not covered" >data;</span>
|
||||
|
||||
// Validation
|
||||
<span class="cstat-no" title="statement not covered" > if (!name || name.trim().length === 0) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('User name is required');</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!email || !User.isValidEmail(email)) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Valid email is required');</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!password || password.length < 6) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Password must be at least 6 characters long');</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return new User(null, name.trim(), email.toLowerCase(), password, new Date(), new Date());</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Email validation
|
||||
* @param {string} email
|
||||
* @returns {boolean}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > st</span>atic isValidEmail(email) {
|
||||
const emailRegex = <span class="cstat-no" title="statement not covered" >/^[^\s@]+@[^\s@]+\.[^\s@]+$/;</span>
|
||||
<span class="cstat-no" title="statement not covered" > return emailRegex.test(email);</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user name
|
||||
* @param {string} newName
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > up</span>dateName(newName) {
|
||||
<span class="cstat-no" title="statement not covered" > if (!newName || newName.trim().length === 0) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('User name is required');</span>
|
||||
}
|
||||
<span class="cstat-no" title="statement not covered" > this.name = newName.trim();</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.updatedAt = new Date();</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user without sensitive data
|
||||
* @returns {Object}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > to</span>PublicJSON() {
|
||||
<span class="cstat-no" title="statement not covered" > return {</span>
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
email: this.email,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = User;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for domain/models</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> domain/models</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/22</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/16</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/5</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/22</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="User.js"><a href="User.js.html">User.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="22" class="abs low">0/22</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="16" class="abs low">0/16</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="22" class="abs low">0/22</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 445 B |
@@ -0,0 +1,266 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for All files</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="prettify.css" />
|
||||
<link rel="stylesheet" href="base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>All files</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0.32% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>1/312</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">1.94% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>2/103</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">4.81% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>4/83</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0.33% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>1/301</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="api/controllers"><a href="api/controllers/index.html">api/controllers</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="65" class="abs low">0/65</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="18" class="abs low">0/18</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="9" class="abs low">0/9</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="65" class="abs low">0/65</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="api/middlewares"><a href="api/middlewares/index.html">api/middlewares</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="17" class="abs low">0/17</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="17" class="abs low">0/17</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="api/routers"><a href="api/routers/index.html">api/routers</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="29" class="abs low">0/29</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="11" class="abs low">0/11</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="20" class="abs low">0/20</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="application/auth/commands"><a href="application/auth/commands/index.html">application/auth/commands</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="49" class="abs low">0/49</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="21" class="abs low">0/21</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="7" class="abs low">0/7</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="49" class="abs low">0/49</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="application/services"><a href="application/services/index.html">application/services</a></td>
|
||||
<td data-value="2.94" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 2%"></div><div class="cover-empty" style="width: 98%"></div></div>
|
||||
</td>
|
||||
<td data-value="2.94" class="pct low">2.94%</td>
|
||||
<td data-value="34" class="abs low">1/34</td>
|
||||
<td data-value="9.09" class="pct low">9.09%</td>
|
||||
<td data-value="22" class="abs low">2/22</td>
|
||||
<td data-value="28.57" class="pct low">28.57%</td>
|
||||
<td data-value="14" class="abs low">4/14</td>
|
||||
<td data-value="2.94" class="pct low">2.94%</td>
|
||||
<td data-value="34" class="abs low">1/34</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="application/user/commands"><a href="application/user/commands/index.html">application/user/commands</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="11" class="abs low">0/11</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="3" class="abs low">0/3</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="11" class="abs low">0/11</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="application/user/queries"><a href="application/user/queries/index.html">application/user/queries</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="28" class="abs low">0/28</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="10" class="abs low">0/10</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="27" class="abs low">0/27</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="domain/irepositories"><a href="domain/irepositories/index.html">domain/irepositories</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="7" class="abs low">0/7</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="6" class="abs low">0/6</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="7" class="abs low">0/7</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="domain/models"><a href="domain/models/index.html">domain/models</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="22" class="abs low">0/22</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="16" class="abs low">0/16</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="22" class="abs low">0/22</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="infrastructure/db"><a href="infrastructure/db/index.html">infrastructure/db</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="infrastructure/repositories"><a href="infrastructure/repositories/index.html">infrastructure/repositories</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="9" class="abs low">0/9</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="24" class="abs low">0/24</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="sorter.js"></script>
|
||||
<script src="block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for infrastructure/db/DatabaseConnection.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">infrastructure/db</a> DatabaseConnection.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/5</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const { PrismaClient } = <span class="cstat-no" title="statement not covered" >require('@prisma/client');</span>
|
||||
|
||||
/**
|
||||
* Database Connection Wrapper
|
||||
* Manages Prisma Client lifecycle
|
||||
*/
|
||||
class DatabaseConnection {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor() {
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = null;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Prisma Client
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync connect() {
|
||||
<span class="cstat-no" title="statement not covered" > if (this.prisma) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > console.log('⚠️ Prisma Client already connected');</span>
|
||||
<span class="cstat-no" title="statement not covered" > return;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = new PrismaClient({</span>
|
||||
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > await this.prisma.$connect();</span>
|
||||
<span class="cstat-no" title="statement not covered" > console.log('✅ Prisma connected to PostgreSQL');</span>
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > console.error('❌ Failed to connect to database:', error);</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw error;</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Prisma Client instance
|
||||
* @returns {PrismaClient}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > ge</span>tClient() {
|
||||
<span class="cstat-no" title="statement not covered" > if (!this.prisma) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Database not connected. Call connect() first.');</span>
|
||||
}
|
||||
<span class="cstat-no" title="statement not covered" > return this.prisma;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Close database connection
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync disconnect() {
|
||||
<span class="cstat-no" title="statement not covered" > if (this.prisma) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > await this.prisma.$disconnect();</span>
|
||||
<span class="cstat-no" title="statement not covered" > console.log('🛑 Prisma disconnected');</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = null;</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync healthCheck() {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
<span class="cstat-no" title="statement not covered" > await this.prisma.$queryRaw`SELECT 1`;</span>
|
||||
<span class="cstat-no" title="statement not covered" > return true;</span>
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > console.error('❌ Database health check failed:', error);</span>
|
||||
<span class="cstat-no" title="statement not covered" > return false;</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
const databaseConnection = <span class="cstat-no" title="statement not covered" >new DatabaseConnection();</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = databaseConnection;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for infrastructure/db</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> infrastructure/db</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/5</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="DatabaseConnection.js"><a href="DatabaseConnection.js.html">DatabaseConnection.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for infrastructure/repositories/UserRepository.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">infrastructure/repositories</a> UserRepository.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/9</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/24</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a>
|
||||
<a name='L78'></a><a href='#L78'>78</a>
|
||||
<a name='L79'></a><a href='#L79'>79</a>
|
||||
<a name='L80'></a><a href='#L80'>80</a>
|
||||
<a name='L81'></a><a href='#L81'>81</a>
|
||||
<a name='L82'></a><a href='#L82'>82</a>
|
||||
<a name='L83'></a><a href='#L83'>83</a>
|
||||
<a name='L84'></a><a href='#L84'>84</a>
|
||||
<a name='L85'></a><a href='#L85'>85</a>
|
||||
<a name='L86'></a><a href='#L86'>86</a>
|
||||
<a name='L87'></a><a href='#L87'>87</a>
|
||||
<a name='L88'></a><a href='#L88'>88</a>
|
||||
<a name='L89'></a><a href='#L89'>89</a>
|
||||
<a name='L90'></a><a href='#L90'>90</a>
|
||||
<a name='L91'></a><a href='#L91'>91</a>
|
||||
<a name='L92'></a><a href='#L92'>92</a>
|
||||
<a name='L93'></a><a href='#L93'>93</a>
|
||||
<a name='L94'></a><a href='#L94'>94</a>
|
||||
<a name='L95'></a><a href='#L95'>95</a>
|
||||
<a name='L96'></a><a href='#L96'>96</a>
|
||||
<a name='L97'></a><a href='#L97'>97</a>
|
||||
<a name='L98'></a><a href='#L98'>98</a>
|
||||
<a name='L99'></a><a href='#L99'>99</a>
|
||||
<a name='L100'></a><a href='#L100'>100</a>
|
||||
<a name='L101'></a><a href='#L101'>101</a>
|
||||
<a name='L102'></a><a href='#L102'>102</a>
|
||||
<a name='L103'></a><a href='#L103'>103</a>
|
||||
<a name='L104'></a><a href='#L104'>104</a>
|
||||
<a name='L105'></a><a href='#L105'>105</a>
|
||||
<a name='L106'></a><a href='#L106'>106</a>
|
||||
<a name='L107'></a><a href='#L107'>107</a>
|
||||
<a name='L108'></a><a href='#L108'>108</a>
|
||||
<a name='L109'></a><a href='#L109'>109</a>
|
||||
<a name='L110'></a><a href='#L110'>110</a>
|
||||
<a name='L111'></a><a href='#L111'>111</a>
|
||||
<a name='L112'></a><a href='#L112'>112</a>
|
||||
<a name='L113'></a><a href='#L113'>113</a>
|
||||
<a name='L114'></a><a href='#L114'>114</a>
|
||||
<a name='L115'></a><a href='#L115'>115</a>
|
||||
<a name='L116'></a><a href='#L116'>116</a>
|
||||
<a name='L117'></a><a href='#L117'>117</a>
|
||||
<a name='L118'></a><a href='#L118'>118</a>
|
||||
<a name='L119'></a><a href='#L119'>119</a>
|
||||
<a name='L120'></a><a href='#L120'>120</a>
|
||||
<a name='L121'></a><a href='#L121'>121</a>
|
||||
<a name='L122'></a><a href='#L122'>122</a>
|
||||
<a name='L123'></a><a href='#L123'>123</a>
|
||||
<a name='L124'></a><a href='#L124'>124</a>
|
||||
<a name='L125'></a><a href='#L125'>125</a>
|
||||
<a name='L126'></a><a href='#L126'>126</a>
|
||||
<a name='L127'></a><a href='#L127'>127</a>
|
||||
<a name='L128'></a><a href='#L128'>128</a>
|
||||
<a name='L129'></a><a href='#L129'>129</a>
|
||||
<a name='L130'></a><a href='#L130'>130</a>
|
||||
<a name='L131'></a><a href='#L131'>131</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const IUserRepository = <span class="cstat-no" title="statement not covered" >require('../../domain/irepositories/IUserRepository');</span>
|
||||
const User = <span class="cstat-no" title="statement not covered" >require('../../domain/models/User');</span>
|
||||
|
||||
/**
|
||||
* User Repository Implementation
|
||||
* Prisma-based data access for User entity
|
||||
*/
|
||||
class UserRepository extends IUserRepository {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(prisma) {
|
||||
<span class="cstat-no" title="statement not covered" > super();</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = prisma;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by ID
|
||||
* @param {number} id
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync findById(id) {
|
||||
const userData = <span class="cstat-no" title="statement not covered" >await this.prisma.user.findUnique({</span>
|
||||
where: { id: parseInt(id) }
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!userData) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return null;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return this._toDomain(userData);</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by email
|
||||
* @param {string} email
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync findByEmail(email) {
|
||||
const userData = <span class="cstat-no" title="statement not covered" >await this.prisma.user.findUnique({</span>
|
||||
where: { email: email.toLowerCase() }
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!userData) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return null;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return this._toDomain(userData);</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all users
|
||||
* @returns {Promise<User[]>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync findAll() {
|
||||
const usersData = <span class="cstat-no" title="statement not covered" >await this.prisma.user.findMany({</span>
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return usersData.map(<span class="fstat-no" title="function not covered" >us</span>erData => <span class="cstat-no" title="statement not covered" >this._toDomain(userData))</span>;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new user
|
||||
* @param {User} user
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync create(user) {
|
||||
const userData = <span class="cstat-no" title="statement not covered" >await this.prisma.user.create({</span>
|
||||
data: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
password: user.password
|
||||
}
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return this._toDomain(userData);</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing user
|
||||
* @param {User} user
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync update(user) {
|
||||
const userData = <span class="cstat-no" title="statement not covered" >await this.prisma.user.update({</span>
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
updatedAt: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return this._toDomain(userData);</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user by ID
|
||||
* @param {number} id
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync delete(id) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
<span class="cstat-no" title="statement not covered" > await this.prisma.user.delete({</span>
|
||||
where: { id: parseInt(id) }
|
||||
});
|
||||
<span class="cstat-no" title="statement not covered" > return true;</span>
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > return false;</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Prisma data to Domain model
|
||||
* @private
|
||||
* @param {Object} userData - Prisma user data
|
||||
* @returns {User}
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > _t</span>oDomain(userData) {
|
||||
<span class="cstat-no" title="statement not covered" > return new User(</span>
|
||||
userData.id,
|
||||
userData.name,
|
||||
userData.email,
|
||||
userData.password,
|
||||
userData.createdAt,
|
||||
userData.updatedAt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = UserRepository;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for infrastructure/repositories</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> infrastructure/repositories</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/25</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/9</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/24</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="UserRepository.js"><a href="UserRepository.js.html">UserRepository.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="25" class="abs low">0/25</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="9" class="abs low">0/9</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="24" class="abs low">0/24</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 138 B |
@@ -0,0 +1,210 @@
|
||||
/* eslint-disable */
|
||||
var addSorting = (function() {
|
||||
'use strict';
|
||||
var cols,
|
||||
currentSort = {
|
||||
index: 0,
|
||||
desc: false
|
||||
};
|
||||
|
||||
// returns the summary table element
|
||||
function getTable() {
|
||||
return document.querySelector('.coverage-summary');
|
||||
}
|
||||
// returns the thead element of the summary table
|
||||
function getTableHeader() {
|
||||
return getTable().querySelector('thead tr');
|
||||
}
|
||||
// returns the tbody element of the summary table
|
||||
function getTableBody() {
|
||||
return getTable().querySelector('tbody');
|
||||
}
|
||||
// returns the th element for nth column
|
||||
function getNthColumn(n) {
|
||||
return getTableHeader().querySelectorAll('th')[n];
|
||||
}
|
||||
|
||||
function onFilterInput() {
|
||||
const searchValue = document.getElementById('fileSearch').value;
|
||||
const rows = document.getElementsByTagName('tbody')[0].children;
|
||||
|
||||
// Try to create a RegExp from the searchValue. If it fails (invalid regex),
|
||||
// it will be treated as a plain text search
|
||||
let searchRegex;
|
||||
try {
|
||||
searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
|
||||
} catch (error) {
|
||||
searchRegex = null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i];
|
||||
let isMatch = false;
|
||||
|
||||
if (searchRegex) {
|
||||
// If a valid regex was created, use it for matching
|
||||
isMatch = searchRegex.test(row.textContent);
|
||||
} else {
|
||||
// Otherwise, fall back to the original plain text search
|
||||
isMatch = row.textContent
|
||||
.toLowerCase()
|
||||
.includes(searchValue.toLowerCase());
|
||||
}
|
||||
|
||||
row.style.display = isMatch ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// loads the search box
|
||||
function addSearchBox() {
|
||||
var template = document.getElementById('filterTemplate');
|
||||
var templateClone = template.content.cloneNode(true);
|
||||
templateClone.getElementById('fileSearch').oninput = onFilterInput;
|
||||
template.parentElement.appendChild(templateClone);
|
||||
}
|
||||
|
||||
// loads all columns
|
||||
function loadColumns() {
|
||||
var colNodes = getTableHeader().querySelectorAll('th'),
|
||||
colNode,
|
||||
cols = [],
|
||||
col,
|
||||
i;
|
||||
|
||||
for (i = 0; i < colNodes.length; i += 1) {
|
||||
colNode = colNodes[i];
|
||||
col = {
|
||||
key: colNode.getAttribute('data-col'),
|
||||
sortable: !colNode.getAttribute('data-nosort'),
|
||||
type: colNode.getAttribute('data-type') || 'string'
|
||||
};
|
||||
cols.push(col);
|
||||
if (col.sortable) {
|
||||
col.defaultDescSort = col.type === 'number';
|
||||
colNode.innerHTML =
|
||||
colNode.innerHTML + '<span class="sorter"></span>';
|
||||
}
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
// attaches a data attribute to every tr element with an object
|
||||
// of data values keyed by column name
|
||||
function loadRowData(tableRow) {
|
||||
var tableCols = tableRow.querySelectorAll('td'),
|
||||
colNode,
|
||||
col,
|
||||
data = {},
|
||||
i,
|
||||
val;
|
||||
for (i = 0; i < tableCols.length; i += 1) {
|
||||
colNode = tableCols[i];
|
||||
col = cols[i];
|
||||
val = colNode.getAttribute('data-value');
|
||||
if (col.type === 'number') {
|
||||
val = Number(val);
|
||||
}
|
||||
data[col.key] = val;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
// loads all row data
|
||||
function loadData() {
|
||||
var rows = getTableBody().querySelectorAll('tr'),
|
||||
i;
|
||||
|
||||
for (i = 0; i < rows.length; i += 1) {
|
||||
rows[i].data = loadRowData(rows[i]);
|
||||
}
|
||||
}
|
||||
// sorts the table using the data for the ith column
|
||||
function sortByIndex(index, desc) {
|
||||
var key = cols[index].key,
|
||||
sorter = function(a, b) {
|
||||
a = a.data[key];
|
||||
b = b.data[key];
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
},
|
||||
finalSorter = sorter,
|
||||
tableBody = document.querySelector('.coverage-summary tbody'),
|
||||
rowNodes = tableBody.querySelectorAll('tr'),
|
||||
rows = [],
|
||||
i;
|
||||
|
||||
if (desc) {
|
||||
finalSorter = function(a, b) {
|
||||
return -1 * sorter(a, b);
|
||||
};
|
||||
}
|
||||
|
||||
for (i = 0; i < rowNodes.length; i += 1) {
|
||||
rows.push(rowNodes[i]);
|
||||
tableBody.removeChild(rowNodes[i]);
|
||||
}
|
||||
|
||||
rows.sort(finalSorter);
|
||||
|
||||
for (i = 0; i < rows.length; i += 1) {
|
||||
tableBody.appendChild(rows[i]);
|
||||
}
|
||||
}
|
||||
// removes sort indicators for current column being sorted
|
||||
function removeSortIndicators() {
|
||||
var col = getNthColumn(currentSort.index),
|
||||
cls = col.className;
|
||||
|
||||
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
|
||||
col.className = cls;
|
||||
}
|
||||
// adds sort indicators for current column being sorted
|
||||
function addSortIndicators() {
|
||||
getNthColumn(currentSort.index).className += currentSort.desc
|
||||
? ' sorted-desc'
|
||||
: ' sorted';
|
||||
}
|
||||
// adds event listeners for all sorter widgets
|
||||
function enableUI() {
|
||||
var i,
|
||||
el,
|
||||
ithSorter = function ithSorter(i) {
|
||||
var col = cols[i];
|
||||
|
||||
return function() {
|
||||
var desc = col.defaultDescSort;
|
||||
|
||||
if (currentSort.index === i) {
|
||||
desc = !currentSort.desc;
|
||||
}
|
||||
sortByIndex(i, desc);
|
||||
removeSortIndicators();
|
||||
currentSort.index = i;
|
||||
currentSort.desc = desc;
|
||||
addSortIndicators();
|
||||
};
|
||||
};
|
||||
for (i = 0; i < cols.length; i += 1) {
|
||||
if (cols[i].sortable) {
|
||||
// add the click event handler on the th so users
|
||||
// dont have to click on those tiny arrows
|
||||
el = getNthColumn(i).querySelector('.sorter').parentElement;
|
||||
if (el.addEventListener) {
|
||||
el.addEventListener('click', ithSorter(i));
|
||||
} else {
|
||||
el.attachEvent('onclick', ithSorter(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// adds sorting functionality to the UI
|
||||
return function() {
|
||||
if (!getTable()) {
|
||||
return;
|
||||
}
|
||||
cols = loadColumns();
|
||||
loadData();
|
||||
addSearchBox();
|
||||
addSortIndicators();
|
||||
enableUI();
|
||||
};
|
||||
})();
|
||||
|
||||
window.addEventListener('load', addSorting);
|
||||
@@ -0,0 +1,804 @@
|
||||
TN:
|
||||
SF:src\api\controllers\AuthController.js
|
||||
FN:9,(anonymous_0)
|
||||
FN:18,(anonymous_1)
|
||||
FN:52,(anonymous_2)
|
||||
FN:84,(anonymous_3)
|
||||
FNF:4
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
DA:1,0
|
||||
DA:2,0
|
||||
DA:10,0
|
||||
DA:11,0
|
||||
DA:12,0
|
||||
DA:19,0
|
||||
DA:20,0
|
||||
DA:22,0
|
||||
DA:23,0
|
||||
DA:26,0
|
||||
DA:32,0
|
||||
DA:40,0
|
||||
DA:45,0
|
||||
DA:53,0
|
||||
DA:54,0
|
||||
DA:56,0
|
||||
DA:57,0
|
||||
DA:60,0
|
||||
DA:66,0
|
||||
DA:74,0
|
||||
DA:77,0
|
||||
DA:85,0
|
||||
DA:87,0
|
||||
DA:94,0
|
||||
DA:98,0
|
||||
DA:103,0
|
||||
LF:26
|
||||
LH:0
|
||||
BRDA:40,0,0,0
|
||||
BRDA:40,0,1,0
|
||||
BRDA:40,1,0,0
|
||||
BRDA:40,1,1,0
|
||||
BRDA:40,1,2,0
|
||||
BRDA:40,1,3,0
|
||||
BRDA:74,2,0,0
|
||||
BRDA:74,2,1,0
|
||||
BRDA:74,3,0,0
|
||||
BRDA:74,3,1,0
|
||||
BRF:10
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\api\controllers\UserController.js
|
||||
FN:11,(anonymous_0)
|
||||
FN:21,(anonymous_1)
|
||||
FN:42,(anonymous_2)
|
||||
FN:60,(anonymous_3)
|
||||
FN:85,(anonymous_4)
|
||||
FNF:5
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
DA:1,0
|
||||
DA:2,0
|
||||
DA:3,0
|
||||
DA:4,0
|
||||
DA:12,0
|
||||
DA:13,0
|
||||
DA:14,0
|
||||
DA:15,0
|
||||
DA:22,0
|
||||
DA:24,0
|
||||
DA:26,0
|
||||
DA:27,0
|
||||
DA:29,0
|
||||
DA:34,0
|
||||
DA:35,0
|
||||
DA:43,0
|
||||
DA:44,0
|
||||
DA:45,0
|
||||
DA:47,0
|
||||
DA:53,0
|
||||
DA:61,0
|
||||
DA:62,0
|
||||
DA:63,0
|
||||
DA:65,0
|
||||
DA:66,0
|
||||
DA:69,0
|
||||
DA:70,0
|
||||
DA:72,0
|
||||
DA:77,0
|
||||
DA:78,0
|
||||
DA:86,0
|
||||
DA:87,0
|
||||
DA:88,0
|
||||
DA:90,0
|
||||
DA:91,0
|
||||
DA:93,0
|
||||
DA:98,0
|
||||
DA:99,0
|
||||
DA:104,0
|
||||
LF:39
|
||||
LH:0
|
||||
BRDA:34,0,0,0
|
||||
BRDA:34,0,1,0
|
||||
BRDA:65,1,0,0
|
||||
BRDA:65,1,1,0
|
||||
BRDA:77,2,0,0
|
||||
BRDA:77,2,1,0
|
||||
BRDA:98,3,0,0
|
||||
BRDA:98,3,1,0
|
||||
BRF:8
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\api\middlewares\authMiddleware.js
|
||||
FN:13,authMiddleware
|
||||
FNF:1
|
||||
FNH:0
|
||||
FNDA:0,authMiddleware
|
||||
DA:1,0
|
||||
DA:3,0
|
||||
DA:14,0
|
||||
DA:16,0
|
||||
DA:18,0
|
||||
DA:19,0
|
||||
DA:26,0
|
||||
DA:29,0
|
||||
DA:35,0
|
||||
DA:37,0
|
||||
DA:44,0
|
||||
LF:11
|
||||
LH:0
|
||||
BRDA:18,0,0,0
|
||||
BRDA:18,0,1,0
|
||||
BRF:2
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\api\middlewares\corsMiddleware.js
|
||||
FN:7,(anonymous_0)
|
||||
FNF:1
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
DA:1,0
|
||||
DA:4,0
|
||||
DA:6,0
|
||||
DA:22,0
|
||||
LF:4
|
||||
LH:0
|
||||
BRDA:4,0,0,0
|
||||
BRDA:4,0,1,0
|
||||
BRF:2
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\api\middlewares\scopeMiddleware.js
|
||||
FN:5,scopeMiddleware
|
||||
FN:6,(anonymous_1)
|
||||
FNF:2
|
||||
FNH:0
|
||||
FNDA:0,scopeMiddleware
|
||||
FNDA:0,(anonymous_1)
|
||||
DA:6,0
|
||||
DA:18,0
|
||||
LF:2
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\api\routers\authRoutes.js
|
||||
FN:9,createAuthRoutes
|
||||
FN:20,(anonymous_1)
|
||||
FN:27,(anonymous_2)
|
||||
FN:33,(anonymous_3)
|
||||
FN:38,(anonymous_4)
|
||||
FNF:5
|
||||
FNH:0
|
||||
FNDA:0,createAuthRoutes
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
DA:1,0
|
||||
DA:10,0
|
||||
DA:13,0
|
||||
DA:20,0
|
||||
DA:27,0
|
||||
DA:33,0
|
||||
DA:38,0
|
||||
DA:40,0
|
||||
DA:43,0
|
||||
LF:9
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\api\routers\userRoutes.js
|
||||
FN:10,createUserRoutes
|
||||
FN:20,(anonymous_1)
|
||||
FN:27,(anonymous_2)
|
||||
FN:33,(anonymous_3)
|
||||
FN:39,(anonymous_4)
|
||||
FN:44,(anonymous_5)
|
||||
FNF:6
|
||||
FNH:0
|
||||
FNDA:0,createUserRoutes
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
FNDA:0,(anonymous_5)
|
||||
DA:1,0
|
||||
DA:2,0
|
||||
DA:11,0
|
||||
DA:14,0
|
||||
DA:20,0
|
||||
DA:27,0
|
||||
DA:33,0
|
||||
DA:39,0
|
||||
DA:44,0
|
||||
DA:46,0
|
||||
DA:49,0
|
||||
LF:11
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\auth\commands\LoginUserCommand.js
|
||||
FN:6,(anonymous_0)
|
||||
FNF:1
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
DA:7,0
|
||||
DA:8,0
|
||||
DA:12,0
|
||||
LF:3
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\auth\commands\LoginUserCommandHandler.js
|
||||
FN:11,(anonymous_0)
|
||||
FN:20,(anonymous_1)
|
||||
FNF:2
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
DA:1,0
|
||||
DA:2,0
|
||||
DA:4,0
|
||||
DA:12,0
|
||||
DA:21,0
|
||||
DA:24,0
|
||||
DA:25,0
|
||||
DA:29,0
|
||||
DA:33,0
|
||||
DA:34,0
|
||||
DA:38,0
|
||||
DA:40,0
|
||||
DA:41,0
|
||||
DA:45,0
|
||||
DA:51,0
|
||||
DA:53,0
|
||||
DA:60,0
|
||||
LF:17
|
||||
LH:0
|
||||
BRDA:24,0,0,0
|
||||
BRDA:24,0,1,0
|
||||
BRDA:24,1,0,0
|
||||
BRDA:24,1,1,0
|
||||
BRDA:33,2,0,0
|
||||
BRDA:33,2,1,0
|
||||
BRDA:40,3,0,0
|
||||
BRDA:40,3,1,0
|
||||
BRF:8
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\auth\commands\RegisterUserCommand.js
|
||||
FN:6,(anonymous_0)
|
||||
FNF:1
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
DA:7,0
|
||||
DA:8,0
|
||||
DA:9,0
|
||||
DA:13,0
|
||||
LF:4
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\auth\commands\RegisterUserCommandHandler.js
|
||||
FN:11,(anonymous_0)
|
||||
FN:21,(anonymous_1)
|
||||
FN:62,(anonymous_2)
|
||||
FNF:3
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
DA:1,0
|
||||
DA:2,0
|
||||
DA:4,0
|
||||
DA:12,0
|
||||
DA:13,0
|
||||
DA:22,0
|
||||
DA:25,0
|
||||
DA:26,0
|
||||
DA:29,0
|
||||
DA:30,0
|
||||
DA:34,0
|
||||
DA:35,0
|
||||
DA:36,0
|
||||
DA:40,0
|
||||
DA:44,0
|
||||
DA:45,0
|
||||
DA:49,0
|
||||
DA:52,0
|
||||
DA:61,0
|
||||
DA:62,0
|
||||
DA:63,0
|
||||
DA:68,0
|
||||
DA:74,0
|
||||
DA:76,0
|
||||
DA:83,0
|
||||
LF:25
|
||||
LH:0
|
||||
BRDA:25,0,0,0
|
||||
BRDA:25,0,1,0
|
||||
BRDA:25,1,0,0
|
||||
BRDA:25,1,1,0
|
||||
BRDA:25,1,2,0
|
||||
BRDA:29,2,0,0
|
||||
BRDA:29,2,1,0
|
||||
BRDA:35,3,0,0
|
||||
BRDA:35,3,1,0
|
||||
BRDA:44,4,0,0
|
||||
BRDA:44,4,1,0
|
||||
BRDA:61,5,0,0
|
||||
BRDA:61,5,1,0
|
||||
BRF:13
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\services\Container.js
|
||||
FN:6,(anonymous_0)
|
||||
FN:17,(anonymous_1)
|
||||
FN:25,(anonymous_2)
|
||||
FN:47,(anonymous_3)
|
||||
FNF:4
|
||||
FNH:4
|
||||
FNDA:22,(anonymous_0)
|
||||
FNDA:22,(anonymous_1)
|
||||
FNDA:19,(anonymous_2)
|
||||
FNDA:9,(anonymous_3)
|
||||
DA:57,1
|
||||
LF:1
|
||||
LH:1
|
||||
BRDA:17,0,0,1
|
||||
BRDA:25,1,0,17
|
||||
BRF:2
|
||||
BRH:2
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\services\EmailService.js
|
||||
FN:8,(anonymous_0)
|
||||
FN:23,(anonymous_1)
|
||||
FNF:2
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
DA:20,0
|
||||
DA:26,0
|
||||
DA:27,0
|
||||
DA:31,0
|
||||
LF:4
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\services\JwtService.js
|
||||
FN:8,(anonymous_0)
|
||||
FN:19,(anonymous_1)
|
||||
FN:28,(anonymous_2)
|
||||
FN:41,(anonymous_3)
|
||||
FN:60,(anonymous_4)
|
||||
FN:72,(anonymous_5)
|
||||
FN:89,(anonymous_6)
|
||||
FN:109,(anonymous_7)
|
||||
FNF:8
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
FNDA:0,(anonymous_5)
|
||||
FNDA:0,(anonymous_6)
|
||||
FNDA:0,(anonymous_7)
|
||||
DA:1,0
|
||||
DA:9,0
|
||||
DA:10,0
|
||||
DA:11,0
|
||||
DA:20,0
|
||||
DA:29,0
|
||||
DA:30,0
|
||||
DA:32,0
|
||||
DA:42,0
|
||||
DA:43,0
|
||||
DA:46,0
|
||||
DA:48,0
|
||||
DA:49,0
|
||||
DA:52,0
|
||||
DA:61,0
|
||||
DA:62,0
|
||||
DA:65,0
|
||||
DA:73,0
|
||||
DA:75,0
|
||||
DA:91,0
|
||||
DA:93,0
|
||||
DA:94,0
|
||||
DA:95,0
|
||||
DA:96,0
|
||||
DA:97,0
|
||||
DA:98,0
|
||||
DA:102,0
|
||||
DA:110,0
|
||||
DA:114,0
|
||||
LF:29
|
||||
LH:0
|
||||
BRDA:9,0,0,0
|
||||
BRDA:9,0,1,0
|
||||
BRDA:10,1,0,0
|
||||
BRDA:10,1,1,0
|
||||
BRDA:42,2,0,0
|
||||
BRDA:42,2,1,0
|
||||
BRDA:48,3,0,0
|
||||
BRDA:48,3,1,0
|
||||
BRDA:48,4,0,0
|
||||
BRDA:48,4,1,0
|
||||
BRDA:61,5,0,0
|
||||
BRDA:61,5,1,0
|
||||
BRDA:61,6,0,0
|
||||
BRDA:61,6,1,0
|
||||
BRDA:93,7,0,0
|
||||
BRDA:93,7,1,0
|
||||
BRDA:95,8,0,0
|
||||
BRDA:95,8,1,0
|
||||
BRDA:97,9,0,0
|
||||
BRDA:97,9,1,0
|
||||
BRF:20
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\user\commands\UpdateUserProfileCommand.js
|
||||
FN:6,(anonymous_0)
|
||||
FNF:1
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
DA:7,0
|
||||
DA:8,0
|
||||
DA:12,0
|
||||
LF:3
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\user\commands\UpdateUserProfileCommandHandler.js
|
||||
FN:6,(anonymous_0)
|
||||
FN:15,(anonymous_1)
|
||||
FNF:2
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
DA:7,0
|
||||
DA:16,0
|
||||
DA:18,0
|
||||
DA:19,0
|
||||
DA:22,0
|
||||
DA:27,0
|
||||
DA:28,0
|
||||
DA:32,0
|
||||
LF:8
|
||||
LH:0
|
||||
BRDA:18,0,0,0
|
||||
BRDA:18,0,1,0
|
||||
BRF:2
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\user\queries\GetAllUsersQuery.js
|
||||
FN:6,(anonymous_0)
|
||||
FNF:1
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
DA:11,0
|
||||
LF:1
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\user\queries\GetAllUsersQueryHandler.js
|
||||
FN:6,(anonymous_0)
|
||||
FN:15,(anonymous_1)
|
||||
FN:21,(anonymous_2)
|
||||
FNF:3
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
DA:7,0
|
||||
DA:16,0
|
||||
DA:21,0
|
||||
DA:25,0
|
||||
LF:4
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\user\queries\GetMeQuery.js
|
||||
FN:6,(anonymous_0)
|
||||
FNF:1
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
DA:7,0
|
||||
DA:11,0
|
||||
LF:2
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\user\queries\GetMeQueryHandler.js
|
||||
FN:6,(anonymous_0)
|
||||
FN:15,(anonymous_1)
|
||||
FNF:2
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
DA:7,0
|
||||
DA:16,0
|
||||
DA:18,0
|
||||
DA:22,0
|
||||
DA:23,0
|
||||
DA:26,0
|
||||
DA:27,0
|
||||
DA:31,0
|
||||
LF:8
|
||||
LH:0
|
||||
BRDA:22,0,0,0
|
||||
BRDA:22,0,1,0
|
||||
BRF:2
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\user\queries\GetUserByIdQuery.js
|
||||
FN:6,(anonymous_0)
|
||||
FNF:1
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
DA:7,0
|
||||
DA:11,0
|
||||
LF:2
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\application\user\queries\GetUserByIdQueryHandler.js
|
||||
FN:6,(anonymous_0)
|
||||
FN:15,(anonymous_1)
|
||||
FNF:2
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
DA:7,0
|
||||
DA:16,0
|
||||
DA:18,0
|
||||
DA:19,0
|
||||
DA:22,0
|
||||
DA:26,0
|
||||
DA:27,0
|
||||
DA:30,0
|
||||
DA:31,0
|
||||
DA:35,0
|
||||
LF:10
|
||||
LH:0
|
||||
BRDA:18,0,0,0
|
||||
BRDA:18,0,1,0
|
||||
BRDA:18,1,0,0
|
||||
BRDA:18,1,1,0
|
||||
BRDA:26,2,0,0
|
||||
BRDA:26,2,1,0
|
||||
BRF:6
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\domain\irepositories\IUserRepository.js
|
||||
FN:11,(anonymous_0)
|
||||
FN:20,(anonymous_1)
|
||||
FN:28,(anonymous_2)
|
||||
FN:37,(anonymous_3)
|
||||
FN:46,(anonymous_4)
|
||||
FN:55,(anonymous_5)
|
||||
FNF:6
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
FNDA:0,(anonymous_5)
|
||||
DA:12,0
|
||||
DA:21,0
|
||||
DA:29,0
|
||||
DA:38,0
|
||||
DA:47,0
|
||||
DA:56,0
|
||||
DA:60,0
|
||||
LF:7
|
||||
LH:0
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\domain\models\User.js
|
||||
FN:6,(anonymous_0)
|
||||
FN:20,(anonymous_1)
|
||||
FN:44,(anonymous_2)
|
||||
FN:53,(anonymous_3)
|
||||
FN:65,(anonymous_4)
|
||||
FNF:5
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
DA:7,0
|
||||
DA:8,0
|
||||
DA:9,0
|
||||
DA:10,0
|
||||
DA:11,0
|
||||
DA:12,0
|
||||
DA:21,0
|
||||
DA:24,0
|
||||
DA:25,0
|
||||
DA:28,0
|
||||
DA:29,0
|
||||
DA:32,0
|
||||
DA:33,0
|
||||
DA:36,0
|
||||
DA:45,0
|
||||
DA:46,0
|
||||
DA:54,0
|
||||
DA:55,0
|
||||
DA:57,0
|
||||
DA:58,0
|
||||
DA:66,0
|
||||
DA:76,0
|
||||
LF:22
|
||||
LH:0
|
||||
BRDA:24,0,0,0
|
||||
BRDA:24,0,1,0
|
||||
BRDA:24,1,0,0
|
||||
BRDA:24,1,1,0
|
||||
BRDA:28,2,0,0
|
||||
BRDA:28,2,1,0
|
||||
BRDA:28,3,0,0
|
||||
BRDA:28,3,1,0
|
||||
BRDA:32,4,0,0
|
||||
BRDA:32,4,1,0
|
||||
BRDA:32,5,0,0
|
||||
BRDA:32,5,1,0
|
||||
BRDA:54,6,0,0
|
||||
BRDA:54,6,1,0
|
||||
BRDA:54,7,0,0
|
||||
BRDA:54,7,1,0
|
||||
BRF:16
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\infrastructure\db\DatabaseConnection.js
|
||||
FN:8,(anonymous_0)
|
||||
FN:15,(anonymous_1)
|
||||
FN:38,(anonymous_2)
|
||||
FN:48,(anonymous_3)
|
||||
FN:60,(anonymous_4)
|
||||
FNF:5
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
DA:1,0
|
||||
DA:9,0
|
||||
DA:16,0
|
||||
DA:17,0
|
||||
DA:18,0
|
||||
DA:21,0
|
||||
DA:22,0
|
||||
DA:26,0
|
||||
DA:27,0
|
||||
DA:29,0
|
||||
DA:30,0
|
||||
DA:39,0
|
||||
DA:40,0
|
||||
DA:42,0
|
||||
DA:49,0
|
||||
DA:50,0
|
||||
DA:51,0
|
||||
DA:52,0
|
||||
DA:61,0
|
||||
DA:62,0
|
||||
DA:63,0
|
||||
DA:65,0
|
||||
DA:66,0
|
||||
DA:72,0
|
||||
DA:74,0
|
||||
LF:25
|
||||
LH:0
|
||||
BRDA:16,0,0,0
|
||||
BRDA:16,0,1,0
|
||||
BRDA:23,1,0,0
|
||||
BRDA:23,1,1,0
|
||||
BRDA:39,2,0,0
|
||||
BRDA:39,2,1,0
|
||||
BRDA:49,3,0,0
|
||||
BRDA:49,3,1,0
|
||||
BRF:8
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src\infrastructure\repositories\UserRepository.js
|
||||
FN:9,(anonymous_0)
|
||||
FN:19,(anonymous_1)
|
||||
FN:36,(anonymous_2)
|
||||
FN:52,(anonymous_3)
|
||||
FN:57,(anonymous_4)
|
||||
FN:65,(anonymous_5)
|
||||
FN:82,(anonymous_6)
|
||||
FN:101,(anonymous_7)
|
||||
FN:118,(anonymous_8)
|
||||
FNF:9
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
FNDA:0,(anonymous_5)
|
||||
FNDA:0,(anonymous_6)
|
||||
FNDA:0,(anonymous_7)
|
||||
FNDA:0,(anonymous_8)
|
||||
DA:1,0
|
||||
DA:2,0
|
||||
DA:10,0
|
||||
DA:11,0
|
||||
DA:20,0
|
||||
DA:24,0
|
||||
DA:25,0
|
||||
DA:28,0
|
||||
DA:37,0
|
||||
DA:41,0
|
||||
DA:42,0
|
||||
DA:45,0
|
||||
DA:53,0
|
||||
DA:57,0
|
||||
DA:66,0
|
||||
DA:74,0
|
||||
DA:83,0
|
||||
DA:93,0
|
||||
DA:102,0
|
||||
DA:103,0
|
||||
DA:106,0
|
||||
DA:108,0
|
||||
DA:119,0
|
||||
DA:130,0
|
||||
LF:24
|
||||
LH:0
|
||||
BRDA:24,0,0,0
|
||||
BRDA:24,0,1,0
|
||||
BRDA:41,1,0,0
|
||||
BRDA:41,1,1,0
|
||||
BRF:4
|
||||
BRH:0
|
||||
end_of_record
|
||||
@@ -0,0 +1,43 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# PostgreSQL Database
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: cors-di-email-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${DB_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
|
||||
POSTGRES_DB: ${DB_NAME:-cors_di_app}
|
||||
ports:
|
||||
- "${DB_PORT:-5432}:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./infrastructure/db/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Node.js Application (opcionális - ha konténerben is futtatod)
|
||||
# app:
|
||||
# build: .
|
||||
# container_name: cors-di-email-app
|
||||
# restart: unless-stopped
|
||||
# ports:
|
||||
# - "${PORT:-3000}:3000"
|
||||
# environment:
|
||||
# - NODE_ENV=development
|
||||
# - DB_HOST=postgres
|
||||
# depends_on:
|
||||
# postgres:
|
||||
# condition: service_healthy
|
||||
# volumes:
|
||||
# - ./src:/app/src
|
||||
# command: npm run dev
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
coverageDirectory: 'coverage',
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.js',
|
||||
'!src/api/server.js',
|
||||
'!src/**/index.js'
|
||||
],
|
||||
testMatch: [
|
||||
'**/tests/**/*.test.js'
|
||||
],
|
||||
verbose: true,
|
||||
clearMocks: true,
|
||||
resetMocks: true,
|
||||
restoreMocks: true
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "cors-di-email-practice",
|
||||
"version": "1.0.0",
|
||||
"description": "CORS + Dependency Injection + Email Notification Practice Project",
|
||||
"main": "src/api/server.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon src/api/server.js",
|
||||
"start": "node src/api/server.js",
|
||||
"docker:up": "docker-compose up -d",
|
||||
"docker:down": "docker-compose down",
|
||||
"docker:logs": "docker-compose logs -f postgres",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev",
|
||||
"prisma:studio": "prisma studio",
|
||||
"test": "jest --coverage",
|
||||
"test:watch": "jest --watch",
|
||||
"test:unit": "jest tests/unit"
|
||||
},
|
||||
"keywords": ["express", "cors", "di", "dependency-injection", "email", "nodemailer", "prisma", "jwt", "authentication"],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"@prisma/client": "^5.7.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"nodemailer": "^6.9.7",
|
||||
"handlebars": "^4.7.8",
|
||||
"dotenv": "^16.3.1",
|
||||
"prisma": "^5.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Prisma Schema - PostgreSQL Database
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// User Model
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
email String @unique
|
||||
password String // bcrypt hashed password
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
const RegisterUserCommand = require('../../application/auth/commands/RegisterUserCommand');
|
||||
const LoginUserCommand = require('../../application/auth/commands/LoginUserCommand');
|
||||
|
||||
/**
|
||||
* Auth Controller
|
||||
* Authentication endpoints using CQRS Commands with cookie-based JWT
|
||||
*/
|
||||
class AuthController {
|
||||
constructor(registerUserCommandHandler, loginUserCommandHandler, jwtService) {
|
||||
this.registerUserCommandHandler = registerUserCommandHandler;
|
||||
this.loginUserCommandHandler = loginUserCommandHandler;
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/register - User regisztráció
|
||||
*/
|
||||
async register(req, res) {
|
||||
try {
|
||||
const { name, email, password } = req.body;
|
||||
|
||||
const command = new RegisterUserCommand(name, email, password);
|
||||
const result = await this.registerUserCommandHandler.handle(command);
|
||||
|
||||
// Set JWT token in httpOnly cookie
|
||||
res.cookie(
|
||||
this.jwtService.getCookieName(),
|
||||
result.token,
|
||||
this.jwtService.getCookieOptions()
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
message: 'User registered successfully',
|
||||
data: {
|
||||
user: result.user
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Validációs hibák -> 400
|
||||
const status = error.message.includes('required') ||
|
||||
error.message.includes('already exists') ||
|
||||
error.message.includes('Invalid') ||
|
||||
error.message.includes('must be') ? 400 : 500;
|
||||
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/login - User bejelentkezés
|
||||
*/
|
||||
async login(req, res) {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const command = new LoginUserCommand(email, password);
|
||||
const result = await this.loginUserCommandHandler.handle(command);
|
||||
|
||||
// Set JWT token in httpOnly cookie
|
||||
res.cookie(
|
||||
this.jwtService.getCookieName(),
|
||||
result.token,
|
||||
this.jwtService.getCookieOptions()
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Login successful',
|
||||
data: {
|
||||
user: result.user
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Validációs vagy auth hibák -> 401
|
||||
const status = error.message.includes('Invalid') ||
|
||||
error.message.includes('required') ? 401 : 500;
|
||||
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/logout - User kijelentkezés
|
||||
*/
|
||||
async logout(req, res) {
|
||||
try {
|
||||
// Clear the auth cookie
|
||||
res.clearCookie(this.jwtService.getCookieName(), {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'strict',
|
||||
path: '/'
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Logout successful'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthController;
|
||||
@@ -0,0 +1,104 @@
|
||||
const GetMeQuery = require('../../application/user/queries/GetMeQuery');
|
||||
const GetAllUsersQuery = require('../../application/user/queries/GetAllUsersQuery');
|
||||
const GetUserByIdQuery = require('../../application/user/queries/GetUserByIdQuery');
|
||||
const UpdateUserProfileCommand = require('../../application/user/commands/UpdateUserProfileCommand');
|
||||
|
||||
/**
|
||||
* User Controller
|
||||
* User-related endpoints using CQRS pattern (protected by JWT)
|
||||
*/
|
||||
class UserController {
|
||||
constructor(getMeQueryHandler, getAllUsersQueryHandler, getUserByIdQueryHandler, updateUserProfileCommandHandler) {
|
||||
this.getMeQueryHandler = getMeQueryHandler;
|
||||
this.getAllUsersQueryHandler = getAllUsersQueryHandler;
|
||||
this.getUserByIdQueryHandler = getUserByIdQueryHandler;
|
||||
this.updateUserProfileCommandHandler = updateUserProfileCommandHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users/me - Bejelentkezett user adatai (protected)
|
||||
*/
|
||||
async getMe(req, res) {
|
||||
try {
|
||||
// req.user-t az authMiddleware tölti ki a JWT-ből
|
||||
const userId = req.user.userId;
|
||||
|
||||
const query = new GetMeQuery(userId);
|
||||
const user = await this.getMeQueryHandler.handle(query);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'User retrieved successfully',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
const status = error.message.includes('not found') ? 404 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users - Összes user lekérése (protected)
|
||||
*/
|
||||
async getAll(req, res) {
|
||||
try {
|
||||
const query = new GetAllUsersQuery();
|
||||
const users = await this.getAllUsersQueryHandler.handle(query);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Users retrieved successfully',
|
||||
data: users,
|
||||
count: users.length
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users/:id - User lekérése ID alapján (protected)
|
||||
*/
|
||||
async getById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = parseInt(id);
|
||||
|
||||
if (isNaN(userId)) {
|
||||
return res.status(400).json({ error: 'Invalid user ID' });
|
||||
}
|
||||
|
||||
const query = new GetUserByIdQuery(userId);
|
||||
const user = await this.getUserByIdQueryHandler.handle(query);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'User retrieved successfully',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
const status = error.message.includes('not found') ? 404 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/users/me - User profil frissítése (protected)
|
||||
*/
|
||||
async updateMe(req, res) {
|
||||
try {
|
||||
const userId = req.user.userId;
|
||||
const { name } = req.body;
|
||||
|
||||
const command = new UpdateUserProfileCommand(userId, name);
|
||||
const user = await this.updateUserProfileCommandHandler.handle(command);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Profile updated successfully',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
const status = error.message.includes('required') ? 400 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserController;
|
||||
@@ -0,0 +1,44 @@
|
||||
const JwtService = require('../../application/services/JwtService');
|
||||
|
||||
const jwtService = new JwtService();
|
||||
|
||||
/**
|
||||
* Authentication Middleware - JWT token ellenőrzés (Cookie-based)
|
||||
*
|
||||
* Ezt a middleware-t használd protected route-okon!
|
||||
*
|
||||
* Példa használat:
|
||||
* router.get('/me', authMiddleware, userController.getMe);
|
||||
*/
|
||||
function authMiddleware(req, res, next) {
|
||||
try {
|
||||
// 1. Token kinyerése cookieból
|
||||
const token = jwtService.extractTokenFromCookies(req.cookies);
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
error: 'Authentication required',
|
||||
message: 'No token provided in cookies'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Token verifikálása
|
||||
const decoded = jwtService.verifyToken(token);
|
||||
|
||||
// 3. User adatok elhelyezése req.user-ben (controller-ek használhatják)
|
||||
req.user = {
|
||||
userId: decoded.userId,
|
||||
email: decoded.email
|
||||
};
|
||||
|
||||
// 4. Folytatás
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({
|
||||
error: 'Authentication failed',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = authMiddleware;
|
||||
@@ -0,0 +1,22 @@
|
||||
const cors = require('cors');
|
||||
|
||||
// Engedélyezett origin-ek whitelist (környezeti változóból)
|
||||
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];
|
||||
|
||||
const corsOptions = {
|
||||
origin: function (origin, callback) {
|
||||
// TODO 1: Ha nincs origin (pl. Postman, curl, backend-backend hívás), engedélyezd
|
||||
// Tipp: if (!origin) return callback(null, true);
|
||||
|
||||
// TODO 2: Ha az origin benne van az allowedOrigins-ban, engedélyezd
|
||||
// Tipp: if (allowedOrigins.includes(origin)) return callback(null, true);
|
||||
|
||||
// TODO 3: Egyébként tiltsd le CORS hibával
|
||||
// Tipp: callback(new Error('Not allowed by CORS'));
|
||||
},
|
||||
credentials: true, // Cookie/Auth header engedélyezése
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization']
|
||||
};
|
||||
|
||||
module.exports = cors(corsOptions);
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Request szintű DI scope létrehozása
|
||||
* Middleware ami minden kéréshez új DI scope-ot készít
|
||||
*/
|
||||
function scopeMiddleware(container) {
|
||||
return (req, res, next) => {
|
||||
// TODO 1: Hozz létre request-specifikus scope-ot
|
||||
// Tipp: const scope = container.createScope();
|
||||
|
||||
// TODO 2: Tárold el a scope-ot req.scope alatt
|
||||
// Tipp: req.scope = scope;
|
||||
|
||||
// TODO 3: Hívd meg a next()-et
|
||||
// Tipp: next();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = scopeMiddleware;
|
||||
@@ -0,0 +1,43 @@
|
||||
const express = require('express');
|
||||
|
||||
/**
|
||||
* Auth Routes
|
||||
* Public endpoints (nincs JWT védelem)
|
||||
*
|
||||
* @param {Container} container - DI Container
|
||||
*/
|
||||
function createAuthRoutes(container) {
|
||||
const router = express.Router();
|
||||
|
||||
// AuthController lekérése a DI Container-ből
|
||||
const authController = container.resolve('AuthController');
|
||||
|
||||
/**
|
||||
* POST /api/auth/register - User regisztráció
|
||||
* Body: { name, email, password }
|
||||
* Response: { user, token }
|
||||
*/
|
||||
router.post('/register', (req, res) => authController.register(req, res));
|
||||
|
||||
/**
|
||||
* POST /api/auth/login - User bejelentkezés
|
||||
* Body: { email, password }
|
||||
* Response: { user, token }
|
||||
*/
|
||||
router.post('/login', (req, res) => authController.login(req, res));
|
||||
|
||||
/**
|
||||
* POST /api/auth/logout - User kijelentkezés
|
||||
* Clears the authentication cookie
|
||||
*/
|
||||
router.post('/logout', (req, res) => authController.logout(req, res));
|
||||
|
||||
/**
|
||||
* OPTIONS /api/auth/* - CORS preflight
|
||||
*/
|
||||
router.options('*', (req, res) => res.sendStatus(204));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
module.exports = createAuthRoutes;
|
||||
@@ -0,0 +1,49 @@
|
||||
const express = require('express');
|
||||
const authMiddleware = require('../middlewares/authMiddleware');
|
||||
|
||||
/**
|
||||
* User Routes
|
||||
* Protected endpoints (JWT authentication required)
|
||||
*
|
||||
* @param {Container} container - DI Container
|
||||
*/
|
||||
function createUserRoutes(container) {
|
||||
const router = express.Router();
|
||||
|
||||
// UserController lekérése a DI Container-ből
|
||||
const userController = container.resolve('UserController');
|
||||
|
||||
/**
|
||||
* GET /api/users/me - Bejelentkezett user adatai
|
||||
* Headers: Authorization: Bearer <token>
|
||||
*/
|
||||
router.get('/me', authMiddleware, (req, res) => userController.getMe(req, res));
|
||||
|
||||
/**
|
||||
* PUT /api/users/me - User profil frissítése
|
||||
* Headers: Authorization: Bearer <token>
|
||||
* Body: { name }
|
||||
*/
|
||||
router.put('/me', authMiddleware, (req, res) => userController.updateMe(req, res));
|
||||
|
||||
/**
|
||||
* GET /api/users - Összes user lekérése (protected)
|
||||
* Headers: Authorization: Bearer <token>
|
||||
*/
|
||||
router.get('/', authMiddleware, (req, res) => userController.getAll(req, res));
|
||||
|
||||
/**
|
||||
* GET /api/users/:id - User lekérése ID alapján (protected)
|
||||
* Headers: Authorization: Bearer <token>
|
||||
*/
|
||||
router.get('/:id', authMiddleware, (req, res) => userController.getById(req, res));
|
||||
|
||||
/**
|
||||
* OPTIONS /api/users/* - CORS preflight
|
||||
*/
|
||||
router.options('*', (req, res) => res.sendStatus(204));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
module.exports = createUserRoutes;
|
||||
@@ -0,0 +1,215 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const cookieParser = require('cookie-parser');
|
||||
|
||||
// Infrastructure
|
||||
const databaseConnection = require('../infrastructure/db/DatabaseConnection');
|
||||
const UserRepository = require('../infrastructure/repositories/UserRepository');
|
||||
|
||||
// Application Services
|
||||
const Container = require('../application/services/Container');
|
||||
const EmailService = require('../application/services/EmailService');
|
||||
const JwtService = require('../application/services/JwtService');
|
||||
|
||||
// Command Handlers
|
||||
const RegisterUserCommandHandler = require('../application/auth/commands/RegisterUserCommandHandler');
|
||||
const LoginUserCommandHandler = require('../application/auth/commands/LoginUserCommandHandler');
|
||||
const UpdateUserProfileCommandHandler = require('../application/user/commands/UpdateUserProfileCommandHandler');
|
||||
|
||||
// Query Handlers
|
||||
const GetMeQueryHandler = require('../application/user/queries/GetMeQueryHandler');
|
||||
const GetAllUsersQueryHandler = require('../application/user/queries/GetAllUsersQueryHandler');
|
||||
const GetUserByIdQueryHandler = require('../application/user/queries/GetUserByIdQueryHandler');
|
||||
|
||||
// Controllers
|
||||
const AuthController = require('./controllers/AuthController');
|
||||
const UserController = require('./controllers/UserController');
|
||||
|
||||
// Middlewares
|
||||
const corsMiddleware = require('./middlewares/corsMiddleware');
|
||||
const scopeMiddleware = require('./middlewares/scopeMiddleware');
|
||||
|
||||
// Routes
|
||||
const createAuthRoutes = require('./routers/authRoutes');
|
||||
const createUserRoutes = require('./routers/userRoutes');
|
||||
|
||||
const app = express();
|
||||
const container = new Container();
|
||||
|
||||
/**
|
||||
* Dependency Injection Setup (CQRS Pattern)
|
||||
*/
|
||||
function setupDependencies() {
|
||||
// Database Connection (singleton)
|
||||
container.register('DatabaseConnection', () => databaseConnection, 'singleton');
|
||||
|
||||
// PrismaClient (singleton)
|
||||
container.register('PrismaClient', () => {
|
||||
return databaseConnection.getClient();
|
||||
}, 'singleton');
|
||||
|
||||
// UserRepository (singleton)
|
||||
container.register('UserRepository', () => {
|
||||
return new UserRepository(container.resolve('PrismaClient'));
|
||||
}, 'singleton');
|
||||
|
||||
// EmailService (singleton) - A DIÁKOK IMPLEMENTÁLJÁK!
|
||||
container.register('EmailService', () => new EmailService(), 'singleton');
|
||||
|
||||
// JwtService (singleton)
|
||||
container.register('JwtService', () => new JwtService(), 'singleton');
|
||||
|
||||
// === Command Handlers ===
|
||||
|
||||
// RegisterUserCommandHandler (singleton)
|
||||
container.register('RegisterUserCommandHandler', () => {
|
||||
return new RegisterUserCommandHandler(
|
||||
container.resolve('PrismaClient'),
|
||||
container.resolve('EmailService')
|
||||
);
|
||||
}, 'singleton');
|
||||
|
||||
// LoginUserCommandHandler (singleton)
|
||||
container.register('LoginUserCommandHandler', () => {
|
||||
return new LoginUserCommandHandler(container.resolve('PrismaClient'));
|
||||
}, 'singleton');
|
||||
|
||||
// UpdateUserProfileCommandHandler (singleton)
|
||||
container.register('UpdateUserProfileCommandHandler', () => {
|
||||
return new UpdateUserProfileCommandHandler(container.resolve('PrismaClient'));
|
||||
}, 'singleton');
|
||||
|
||||
// === Query Handlers ===
|
||||
|
||||
// GetMeQueryHandler (singleton)
|
||||
container.register('GetMeQueryHandler', () => {
|
||||
return new GetMeQueryHandler(container.resolve('PrismaClient'));
|
||||
}, 'singleton');
|
||||
|
||||
// GetAllUsersQueryHandler (singleton)
|
||||
container.register('GetAllUsersQueryHandler', () => {
|
||||
return new GetAllUsersQueryHandler(container.resolve('PrismaClient'));
|
||||
}, 'singleton');
|
||||
|
||||
// GetUserByIdQueryHandler (singleton)
|
||||
container.register('GetUserByIdQueryHandler', () => {
|
||||
return new GetUserByIdQueryHandler(container.resolve('PrismaClient'));
|
||||
}, 'singleton');
|
||||
|
||||
// === Controllers ===
|
||||
|
||||
// AuthController (singleton)
|
||||
container.register('AuthController', () => {
|
||||
return new AuthController(
|
||||
container.resolve('RegisterUserCommandHandler'),
|
||||
container.resolve('LoginUserCommandHandler'),
|
||||
container.resolve('JwtService')
|
||||
);
|
||||
}, 'singleton');
|
||||
|
||||
// UserController (singleton)
|
||||
container.register('UserController', () => {
|
||||
return new UserController(
|
||||
container.resolve('GetMeQueryHandler'),
|
||||
container.resolve('GetAllUsersQueryHandler'),
|
||||
container.resolve('GetUserByIdQueryHandler'),
|
||||
container.resolve('UpdateUserProfileCommandHandler')
|
||||
);
|
||||
}, 'singleton');
|
||||
|
||||
console.log('✅ DI Container configured (CQRS pattern)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Express Middleware Chain
|
||||
*/
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(cookieParser());
|
||||
|
||||
// TODO (DIÁKOK): Alkalmazd a CORS middleware-t
|
||||
// Tipp: app.use(corsMiddleware);
|
||||
|
||||
// TODO (DIÁKOK): Alkalmazd a Scope middleware-t (opcionális)
|
||||
// Tipp: app.use(scopeMiddleware(container));
|
||||
|
||||
/**
|
||||
* Routes
|
||||
*/
|
||||
app.use('/api/auth', createAuthRoutes(container));
|
||||
app.use('/api/users', createUserRoutes(container));
|
||||
|
||||
/**
|
||||
* Health Check Endpoint
|
||||
*/
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).json({
|
||||
status: 'OK',
|
||||
message: 'Server is running',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Error Handling Middleware
|
||||
*/
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('❌ Error:', err.message);
|
||||
res.status(err.status || 500).json({
|
||||
error: err.message || 'Internal Server Error'
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 404 Handler
|
||||
*/
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({ error: 'Route not found' });
|
||||
});
|
||||
|
||||
/**
|
||||
* Server Startup
|
||||
*/
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
async function startServer() {
|
||||
try {
|
||||
// Database connection
|
||||
await databaseConnection.connect();
|
||||
|
||||
// DI Container inicializálása
|
||||
setupDependencies();
|
||||
|
||||
// Server indítása
|
||||
app.listen(PORT, () => {
|
||||
console.log('🚀 Server running on http://localhost:' + PORT);
|
||||
console.log('📧 Email Service configured (implement EmailService!)');
|
||||
console.log('🔐 JWT Authentication enabled (cookie-based)');
|
||||
console.log('🍪 Cookies: httpOnly, secure (production), sameSite=strict');
|
||||
console.log('');
|
||||
console.log('📍 Endpoints:');
|
||||
console.log(' POST /api/auth/register');
|
||||
console.log(' POST /api/auth/login');
|
||||
console.log(' POST /api/auth/logout');
|
||||
console.log(' GET /api/users/me (protected)');
|
||||
console.log(' PUT /api/users/me (protected)');
|
||||
console.log(' GET /api/users (protected)');
|
||||
console.log(' GET /api/users/:id (protected)');
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to start server:', error);
|
||||
await databaseConnection.disconnect();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\n🛑 Shutting down gracefully...');
|
||||
await databaseConnection.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
startServer();
|
||||
|
||||
module.exports = app;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Login User Command
|
||||
* Command object for user login
|
||||
*/
|
||||
class LoginUserCommand {
|
||||
constructor(email, password) {
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LoginUserCommand;
|
||||
@@ -0,0 +1,60 @@
|
||||
const bcrypt = require('bcryptjs');
|
||||
const JwtService = require('../../services/JwtService');
|
||||
|
||||
const jwtService = new JwtService();
|
||||
|
||||
/**
|
||||
* Login User Command Handler
|
||||
* Handles user login authentication
|
||||
*/
|
||||
class LoginUserCommandHandler {
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute login command
|
||||
* @param {LoginUserCommand} command
|
||||
* @returns {Promise<Object>} { user, token }
|
||||
*/
|
||||
async handle(command) {
|
||||
const { email, password } = command;
|
||||
|
||||
// Validáció
|
||||
if (!email || !password) {
|
||||
throw new Error('Email and password are required');
|
||||
}
|
||||
|
||||
// User keresése email alapján
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { email }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('Invalid email or password');
|
||||
}
|
||||
|
||||
// Jelszó ellenőrzése
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
throw new Error('Invalid email or password');
|
||||
}
|
||||
|
||||
// JWT token generálása
|
||||
const token = jwtService.generateToken({
|
||||
userId: user.id,
|
||||
email: user.email
|
||||
});
|
||||
|
||||
// Jelszót ne adjuk vissza
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
|
||||
return {
|
||||
user: userWithoutPassword,
|
||||
token
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LoginUserCommandHandler;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Register User Command
|
||||
* Command object for user registration
|
||||
*/
|
||||
class RegisterUserCommand {
|
||||
constructor(name, email, password) {
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RegisterUserCommand;
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
const bcrypt = require('bcryptjs');
|
||||
const JwtService = require('../../services/JwtService');
|
||||
|
||||
const jwtService = new JwtService();
|
||||
|
||||
/**
|
||||
* Register User Command Handler
|
||||
* Handles user registration business logic
|
||||
*/
|
||||
class RegisterUserCommandHandler {
|
||||
constructor(prisma, emailService) {
|
||||
this.prisma = prisma;
|
||||
this.emailService = emailService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute user registration command
|
||||
* @param {RegisterUserCommand} command
|
||||
* @returns {Promise<Object>} { user, token }
|
||||
*/
|
||||
async handle(command) {
|
||||
const { name, email, password } = command;
|
||||
|
||||
// Validáció
|
||||
if (!name || !email || !password) {
|
||||
throw new Error('Name, email and password are required');
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
throw new Error('Password must be at least 6 characters long');
|
||||
}
|
||||
|
||||
// Email formátum ellenőrzés
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
throw new Error('Invalid email format');
|
||||
}
|
||||
|
||||
// Ellenőrizzük, hogy létezik-e már a user
|
||||
const existingUser = await this.prisma.user.findUnique({
|
||||
where: { email }
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
throw new Error('User with this email already exists');
|
||||
}
|
||||
|
||||
// Jelszó hashelése (bcrypt)
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// User létrehozása
|
||||
const user = await this.prisma.user.create({
|
||||
data: {
|
||||
name,
|
||||
email,
|
||||
password: hashedPassword
|
||||
}
|
||||
});
|
||||
|
||||
// Welcome email küldése (async, nem várunk rá)
|
||||
if (this.emailService) {
|
||||
this.emailService.sendWelcomeEmail(email, name).catch(err => {
|
||||
console.error('❌ Failed to send welcome email:', err.message);
|
||||
});
|
||||
}
|
||||
|
||||
// JWT token generálása
|
||||
const token = jwtService.generateToken({
|
||||
userId: user.id,
|
||||
email: user.email
|
||||
});
|
||||
|
||||
// Jelszót ne adjuk vissza a response-ban
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
|
||||
return {
|
||||
user: userWithoutPassword,
|
||||
token
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RegisterUserCommandHandler;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Dependency Injection Container
|
||||
* Supports singleton, transient, and scoped lifetimes
|
||||
*/
|
||||
class Container {
|
||||
constructor() {
|
||||
// TODO 1: Inicializáld a services Map-et (singleton instance-ok tárolása)
|
||||
// TODO 2: Inicializáld a factories Map-et (factory függvények tárolása)
|
||||
// TODO 3: Inicializáld a lifetimes Map-et (lifecycle típusok tárolása)
|
||||
|
||||
// Példa inicializálás:
|
||||
// this.services = new Map();
|
||||
// this.factories = new Map();
|
||||
// this.lifetimes = new Map();
|
||||
}
|
||||
|
||||
register(name, factory, lifetime = 'singleton') {
|
||||
// TODO 4: Tárold el a factory függvényt (this.factories.set(name, factory))
|
||||
// TODO 5: Tárold el a lifetime típust (this.lifetimes.set(name, lifetime))
|
||||
// TODO 6: Ha a lifetime === 'singleton', azonnal példányosítsd:
|
||||
// - Hívd meg a factory-t: const instance = factory();
|
||||
// - Tárold el: this.services.set(name, instance);
|
||||
}
|
||||
|
||||
resolve(name, scope = null) {
|
||||
// TODO 7: Ha a service regisztrálva van mint 'scoped' ÉS van scope paraméter:
|
||||
// - Ellenőrizd: if (scope && scope.has(name)) return scope.get(name);
|
||||
// - Ha nincs még a scope-ban, példányosítsd és tárold:
|
||||
// const instance = this.factories.get(name)();
|
||||
// scope.set(name, instance);
|
||||
// return instance;
|
||||
|
||||
// TODO 8: Ha singleton, add vissza a services-ből:
|
||||
// if (this.lifetimes.get(name) === 'singleton') {
|
||||
// return this.services.get(name);
|
||||
// }
|
||||
|
||||
// TODO 9: Ha transient, minden alkalommal hívj egy új factory-t:
|
||||
// if (this.lifetimes.get(name) === 'transient') {
|
||||
// return this.factories.get(name)();
|
||||
// }
|
||||
|
||||
// TODO 10: Ha nem regisztrált a service, dobj hibát:
|
||||
// throw new Error(`Service '${name}' is not registered`);
|
||||
}
|
||||
|
||||
createScope() {
|
||||
// TODO 11: Hozz létre egy új Map-et az scoped instance-oknak
|
||||
// TODO 12: Térj vissza egy objektummal ami tartalmaz egy resolve metódust:
|
||||
// const scopeMap = new Map();
|
||||
// return {
|
||||
// resolve: (name) => this.resolve(name, scopeMap)
|
||||
// };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Container;
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Email Service
|
||||
* Nodemailer + Handlebars template based email sending
|
||||
*
|
||||
* TODO: Implement by students
|
||||
*/
|
||||
class EmailService {
|
||||
constructor() {
|
||||
// TODO 1: Hozz létre Nodemailer transportot Ethereal tesztelő SMTP-vel
|
||||
// this.transporter = nodemailer.createTransport({
|
||||
// host: 'smtp.ethereal.email',
|
||||
// port: 587,
|
||||
// secure: false, // TLS
|
||||
// auth: {
|
||||
// user: process.env.ETHEREAL_USER || 'your-test-email@ethereal.email',
|
||||
// pass: process.env.ETHEREAL_PASS || 'your-test-password'
|
||||
// }
|
||||
// });
|
||||
|
||||
console.log('📧 EmailService initialized');
|
||||
}
|
||||
|
||||
async sendWelcomeEmail(userEmail, userName) {
|
||||
// TODO 2-6: Implement email sending with Handlebars template
|
||||
// For now, just log to console
|
||||
console.log(`📧 Would send welcome email to ${userEmail} (${userName})`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EmailService;
|
||||
@@ -0,0 +1,114 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
/**
|
||||
* JWT Service
|
||||
* Handles JWT token generation, verification, and cookie management
|
||||
*/
|
||||
class JwtService {
|
||||
constructor() {
|
||||
this.secret = process.env.JWT_SECRET || 'default-secret-change-me';
|
||||
this.expiresIn = process.env.JWT_EXPIRES_IN || '1h';
|
||||
this.cookieName = 'auth_token';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JWT token
|
||||
* @param {Object} payload - { userId, email }
|
||||
* @returns {string} JWT token
|
||||
*/
|
||||
generateToken(payload) {
|
||||
return jwt.sign(payload, this.secret, { expiresIn: this.expiresIn });
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify JWT token
|
||||
* @param {string} token - JWT token
|
||||
* @returns {Object} Decoded payload
|
||||
*/
|
||||
verifyToken(token) {
|
||||
try {
|
||||
return jwt.verify(token, this.secret);
|
||||
} catch (error) {
|
||||
throw new Error('Invalid or expired token');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract token from Authorization header (Bearer token)
|
||||
* @param {string} authHeader - Authorization header value
|
||||
* @returns {string|null} Token or null
|
||||
*/
|
||||
extractTokenFromHeader(authHeader) {
|
||||
if (!authHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parts = authHeader.split(' ');
|
||||
|
||||
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract token from cookies
|
||||
* @param {Object} cookies - Request cookies object
|
||||
* @returns {string|null} Token or null
|
||||
*/
|
||||
extractTokenFromCookies(cookies) {
|
||||
if (!cookies || !cookies[this.cookieName]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cookies[this.cookieName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookie options for setting JWT cookie
|
||||
* @returns {Object} Cookie options
|
||||
*/
|
||||
getCookieOptions() {
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
return {
|
||||
httpOnly: true, // Prevents XSS attacks
|
||||
secure: isProduction, // HTTPS only in production
|
||||
sameSite: 'strict', // CSRF protection
|
||||
maxAge: this._getMaxAge(),
|
||||
path: '/'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookie max age in milliseconds
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
_getMaxAge() {
|
||||
// Parse JWT_EXPIRES_IN (e.g., "1h", "7d")
|
||||
const expiresIn = this.expiresIn;
|
||||
|
||||
if (expiresIn.endsWith('h')) {
|
||||
return parseInt(expiresIn) * 60 * 60 * 1000;
|
||||
} else if (expiresIn.endsWith('d')) {
|
||||
return parseInt(expiresIn) * 24 * 60 * 60 * 1000;
|
||||
} else if (expiresIn.endsWith('m')) {
|
||||
return parseInt(expiresIn) * 60 * 1000;
|
||||
}
|
||||
|
||||
// Default: 1 hour
|
||||
return 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookie name
|
||||
* @returns {string}
|
||||
*/
|
||||
getCookieName() {
|
||||
return this.cookieName;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JwtService;
|
||||
@@ -0,0 +1,145 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Üdvözlünk!</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Arial', sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.content {
|
||||
padding: 40px 30px;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.content h2 {
|
||||
color: #667eea;
|
||||
font-size: 22px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.content p {
|
||||
font-size: 16px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
margin: 30px 0;
|
||||
padding: 15px 40px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.cta-button:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.features {
|
||||
background-color: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.features ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.features li {
|
||||
padding: 10px 0;
|
||||
padding-left: 30px;
|
||||
position: relative;
|
||||
}
|
||||
.features li:before {
|
||||
content: "✓";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #667eea;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
.footer {
|
||||
background-color: #f4f4f4;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #777777;
|
||||
font-size: 14px;
|
||||
}
|
||||
.footer a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<h1>🎉 Üdvözlünk, {{userName}}! 🎉</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h2>Sikeres regisztráció!</h2>
|
||||
|
||||
<p>Kedves <strong>{{userName}}</strong>,</p>
|
||||
|
||||
<p>Örömmel értesítünk, hogy a regisztrációd sikeresen megtörtént az alkalmazásunkban! 🚀</p>
|
||||
|
||||
<div class="features">
|
||||
<p><strong>Mit tehetsz most:</strong></p>
|
||||
<ul>
|
||||
<li>Frissítsd a profilodat</li>
|
||||
<li>Fedezd fel az összes funkciót</li>
|
||||
<li>Csatlakozz a közösségünkhöz</li>
|
||||
<li>Kezdd el használni a szolgáltatásainkat</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p style="text-align: center;">
|
||||
<a href="#" class="cta-button">Kezdj neki!</a>
|
||||
</p>
|
||||
|
||||
<p>Ha bármilyen kérdésed van, ne habozz kapcsolatba lépni velünk!</p>
|
||||
|
||||
<p style="margin-top: 30px;">
|
||||
Üdvözlettel,<br>
|
||||
<strong>Az Alkalmazás Csapata</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>
|
||||
Ez egy automatikus üzenet. Kérjük, ne válaszolj erre az e-mailre.<br>
|
||||
<a href="#">Adatvédelmi irányelvek</a> | <a href="#">Felhasználási feltételek</a>
|
||||
</p>
|
||||
<p style="margin-top: 10px; color: #999999;">
|
||||
Email címed: <strong>{{userEmail}}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Update User Profile Command
|
||||
* Command object for updating user profile
|
||||
*/
|
||||
class UpdateUserProfileCommand {
|
||||
constructor(userId, name) {
|
||||
this.userId = userId;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateUserProfileCommand;
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Update User Profile Command Handler
|
||||
* Handles user profile update logic
|
||||
*/
|
||||
class UpdateUserProfileCommandHandler {
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute update profile command
|
||||
* @param {UpdateUserProfileCommand} command
|
||||
* @returns {Promise<Object>} Updated user data
|
||||
*/
|
||||
async handle(command) {
|
||||
const { userId, name } = command;
|
||||
|
||||
if (!name) {
|
||||
throw new Error('Name is required');
|
||||
}
|
||||
|
||||
const user = await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { name }
|
||||
});
|
||||
|
||||
const { password, ...userWithoutPassword } = user;
|
||||
return userWithoutPassword;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateUserProfileCommandHandler;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Get All Users Query
|
||||
* Query object for retrieving all users
|
||||
*/
|
||||
class GetAllUsersQuery {
|
||||
constructor() {
|
||||
// No parameters needed for getting all users
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GetAllUsersQuery;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Get All Users Query Handler
|
||||
* Handles retrieval of all users
|
||||
*/
|
||||
class GetAllUsersQueryHandler {
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute get all users query
|
||||
* @param {GetAllUsersQuery} query
|
||||
* @returns {Promise<Array>} List of users without passwords
|
||||
*/
|
||||
async handle(query) {
|
||||
const users = await this.prisma.user.findMany({
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
// Remove passwords from all users
|
||||
return users.map(({ password, ...user }) => user);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GetAllUsersQueryHandler;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Get Me Query
|
||||
* Query object for getting current authenticated user
|
||||
*/
|
||||
class GetMeQuery {
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GetMeQuery;
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Get Me Query Handler
|
||||
* Handles retrieval of current authenticated user
|
||||
*/
|
||||
class GetMeQueryHandler {
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute get me query
|
||||
* @param {GetMeQuery} query
|
||||
* @returns {Promise<Object>} User data without password
|
||||
*/
|
||||
async handle(query) {
|
||||
const { userId } = query;
|
||||
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const { password, ...userWithoutPassword } = user;
|
||||
return userWithoutPassword;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GetMeQueryHandler;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Get User By ID Query
|
||||
* Query object for retrieving a user by ID
|
||||
*/
|
||||
class GetUserByIdQuery {
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GetUserByIdQuery;
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Get User By ID Query Handler
|
||||
* Handles retrieval of a specific user by ID
|
||||
*/
|
||||
class GetUserByIdQueryHandler {
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute get user by ID query
|
||||
* @param {GetUserByIdQuery} query
|
||||
* @returns {Promise<Object>} User data without password
|
||||
*/
|
||||
async handle(query) {
|
||||
const { userId } = query;
|
||||
|
||||
if (!userId || isNaN(userId)) {
|
||||
throw new Error('Valid user ID is required');
|
||||
}
|
||||
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: parseInt(userId) }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const { password, ...userWithoutPassword } = user;
|
||||
return userWithoutPassword;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GetUserByIdQueryHandler;
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* User Repository Interface
|
||||
* Defines contract for user data access
|
||||
*/
|
||||
class IUserRepository {
|
||||
/**
|
||||
* Find user by ID
|
||||
* @param {number} id
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
async findById(id) {
|
||||
throw new Error('Method findById() must be implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by email
|
||||
* @param {string} email
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
async findByEmail(email) {
|
||||
throw new Error('Method findByEmail() must be implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all users
|
||||
* @returns {Promise<User[]>}
|
||||
*/
|
||||
async findAll() {
|
||||
throw new Error('Method findAll() must be implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new user
|
||||
* @param {User} user
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async create(user) {
|
||||
throw new Error('Method create() must be implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing user
|
||||
* @param {User} user
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async update(user) {
|
||||
throw new Error('Method update() must be implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user by ID
|
||||
* @param {number} id
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async delete(id) {
|
||||
throw new Error('Method delete() must be implemented');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IUserRepository;
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* User Domain Model
|
||||
* Pure domain entity with business logic validation
|
||||
*/
|
||||
class User {
|
||||
constructor(id, name, email, password, createdAt, updatedAt) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new User
|
||||
* @param {Object} data - { name, email, password }
|
||||
* @returns {User}
|
||||
*/
|
||||
static create(data) {
|
||||
const { name, email, password } = data;
|
||||
|
||||
// Validation
|
||||
if (!name || name.trim().length === 0) {
|
||||
throw new Error('User name is required');
|
||||
}
|
||||
|
||||
if (!email || !User.isValidEmail(email)) {
|
||||
throw new Error('Valid email is required');
|
||||
}
|
||||
|
||||
if (!password || password.length < 6) {
|
||||
throw new Error('Password must be at least 6 characters long');
|
||||
}
|
||||
|
||||
return new User(null, name.trim(), email.toLowerCase(), password, new Date(), new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Email validation
|
||||
* @param {string} email
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isValidEmail(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user name
|
||||
* @param {string} newName
|
||||
*/
|
||||
updateName(newName) {
|
||||
if (!newName || newName.trim().length === 0) {
|
||||
throw new Error('User name is required');
|
||||
}
|
||||
this.name = newName.trim();
|
||||
this.updatedAt = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user without sensitive data
|
||||
* @returns {Object}
|
||||
*/
|
||||
toPublicJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
email: this.email,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = User;
|
||||
@@ -0,0 +1,74 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
||||
/**
|
||||
* Database Connection Wrapper
|
||||
* Manages Prisma Client lifecycle
|
||||
*/
|
||||
class DatabaseConnection {
|
||||
constructor() {
|
||||
this.prisma = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Prisma Client
|
||||
*/
|
||||
async connect() {
|
||||
if (this.prisma) {
|
||||
console.log('⚠️ Prisma Client already connected');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.prisma = new PrismaClient({
|
||||
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
});
|
||||
|
||||
await this.prisma.$connect();
|
||||
console.log('✅ Prisma connected to PostgreSQL');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to connect to database:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Prisma Client instance
|
||||
* @returns {PrismaClient}
|
||||
*/
|
||||
getClient() {
|
||||
if (!this.prisma) {
|
||||
throw new Error('Database not connected. Call connect() first.');
|
||||
}
|
||||
return this.prisma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close database connection
|
||||
*/
|
||||
async disconnect() {
|
||||
if (this.prisma) {
|
||||
await this.prisma.$disconnect();
|
||||
console.log('🛑 Prisma disconnected');
|
||||
this.prisma = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async healthCheck() {
|
||||
try {
|
||||
await this.prisma.$queryRaw`SELECT 1`;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Database health check failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
const databaseConnection = new DatabaseConnection();
|
||||
|
||||
module.exports = databaseConnection;
|
||||
@@ -0,0 +1,130 @@
|
||||
const IUserRepository = require('../../domain/irepositories/IUserRepository');
|
||||
const User = require('../../domain/models/User');
|
||||
|
||||
/**
|
||||
* User Repository Implementation
|
||||
* Prisma-based data access for User entity
|
||||
*/
|
||||
class UserRepository extends IUserRepository {
|
||||
constructor(prisma) {
|
||||
super();
|
||||
this.prisma = prisma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by ID
|
||||
* @param {number} id
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
async findById(id) {
|
||||
const userData = await this.prisma.user.findUnique({
|
||||
where: { id: parseInt(id) }
|
||||
});
|
||||
|
||||
if (!userData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._toDomain(userData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by email
|
||||
* @param {string} email
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
async findByEmail(email) {
|
||||
const userData = await this.prisma.user.findUnique({
|
||||
where: { email: email.toLowerCase() }
|
||||
});
|
||||
|
||||
if (!userData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._toDomain(userData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all users
|
||||
* @returns {Promise<User[]>}
|
||||
*/
|
||||
async findAll() {
|
||||
const usersData = await this.prisma.user.findMany({
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
return usersData.map(userData => this._toDomain(userData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new user
|
||||
* @param {User} user
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async create(user) {
|
||||
const userData = await this.prisma.user.create({
|
||||
data: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
password: user.password
|
||||
}
|
||||
});
|
||||
|
||||
return this._toDomain(userData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing user
|
||||
* @param {User} user
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async update(user) {
|
||||
const userData = await this.prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
updatedAt: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
return this._toDomain(userData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user by ID
|
||||
* @param {number} id
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async delete(id) {
|
||||
try {
|
||||
await this.prisma.user.delete({
|
||||
where: { id: parseInt(id) }
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Prisma data to Domain model
|
||||
* @private
|
||||
* @param {Object} userData - Prisma user data
|
||||
* @returns {User}
|
||||
*/
|
||||
_toDomain(userData) {
|
||||
return new User(
|
||||
userData.id,
|
||||
userData.name,
|
||||
userData.email,
|
||||
userData.password,
|
||||
userData.createdAt,
|
||||
userData.updatedAt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRepository;
|
||||
@@ -0,0 +1,39 @@
|
||||
const { expect } = require('chai');
|
||||
const Container = require('../src/core/Container');
|
||||
|
||||
describe('DI Container', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
container = new Container();
|
||||
});
|
||||
|
||||
it('should register and resolve a singleton service', () => {
|
||||
// TODO 1: Teszt: singleton service mindig ugyanazt az instance-t adja vissza
|
||||
// 1. Regisztrálj egy service-t: container.register('TestService', () => ({ id: Math.random() }), 'singleton');
|
||||
// 2. Resolve-old kétszer: const instance1 = container.resolve('TestService');
|
||||
// 3. Ellenőrizd, hogy ugyanaz az instance: expect(instance1).to.equal(instance2);
|
||||
});
|
||||
|
||||
it('should create new instance for transient service', () => {
|
||||
// TODO 2: Teszt: transient service mindig új instance-t ad vissza
|
||||
// 1. Regisztrálj egy transient service-t: container.register('TransientService', () => ({ id: Math.random() }), 'transient');
|
||||
// 2. Resolve-old kétszer
|
||||
// 3. Ellenőrizd, hogy különböző instance-ok: expect(instance1).to.not.equal(instance2);
|
||||
});
|
||||
|
||||
it('should resolve scoped service within scope', () => {
|
||||
// TODO 3: Teszt: scoped service scope-on belül ugyanaz, kívül más instance
|
||||
// 1. Regisztrálj egy scoped service-t: container.register('ScopedService', () => ({ id: Math.random() }), 'scoped');
|
||||
// 2. Hozz létre egy scope-ot: const scope1 = container.createScope();
|
||||
// 3. Resolve-old kétszer ugyanabban a scope-ban
|
||||
// 4. Ellenőrizd, hogy ugyanaz az instance
|
||||
// 5. Hozz létre új scope-ot és resolve-old ott is
|
||||
// 6. Ellenőrizd, hogy különböző instance-ok a két scope között
|
||||
});
|
||||
|
||||
it('should throw error for unregistered service', () => {
|
||||
// TODO 4: Teszt: nem regisztrált service resolve hiba dobása
|
||||
// Tipp: expect(() => container.resolve('NonExistentService')).to.throw();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,311 @@
|
||||
const Container = require('../../../src/application/services/Container');
|
||||
|
||||
describe('Container - Dependency Injection', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
container = new Container();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
test('should initialize with empty Maps', () => {
|
||||
expect(container.services).toBeInstanceOf(Map);
|
||||
expect(container.factories).toBeInstanceOf(Map);
|
||||
expect(container.lifetimes).toBeInstanceOf(Map);
|
||||
expect(container.services.size).toBe(0);
|
||||
expect(container.factories.size).toBe(0);
|
||||
expect(container.lifetimes.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
test('should register a singleton service', () => {
|
||||
const factory = jest.fn(() => ({ id: 1, name: 'Test Service' }));
|
||||
|
||||
container.register('TestService', factory, 'singleton');
|
||||
|
||||
expect(container.factories.has('TestService')).toBe(true);
|
||||
expect(container.lifetimes.get('TestService')).toBe('singleton');
|
||||
expect(factory).toHaveBeenCalled(); // Singleton is created immediately
|
||||
expect(container.services.has('TestService')).toBe(true);
|
||||
});
|
||||
|
||||
test('should register a transient service', () => {
|
||||
const factory = jest.fn(() => ({ id: 2, name: 'Transient Service' }));
|
||||
|
||||
container.register('TransientService', factory, 'transient');
|
||||
|
||||
expect(container.factories.has('TransientService')).toBe(true);
|
||||
expect(container.lifetimes.get('TransientService')).toBe('transient');
|
||||
expect(factory).not.toHaveBeenCalled(); // Transient is NOT created immediately
|
||||
expect(container.services.has('TransientService')).toBe(false);
|
||||
});
|
||||
|
||||
test('should register a scoped service', () => {
|
||||
const factory = jest.fn(() => ({ id: 3, name: 'Scoped Service' }));
|
||||
|
||||
container.register('ScopedService', factory, 'scoped');
|
||||
|
||||
expect(container.factories.has('ScopedService')).toBe(true);
|
||||
expect(container.lifetimes.get('ScopedService')).toBe('scoped');
|
||||
expect(factory).not.toHaveBeenCalled(); // Scoped is NOT created immediately
|
||||
});
|
||||
|
||||
test('should default to singleton if lifetime not specified', () => {
|
||||
const factory = () => ({ id: 4 });
|
||||
|
||||
container.register('DefaultService', factory);
|
||||
|
||||
expect(container.lifetimes.get('DefaultService')).toBe('singleton');
|
||||
expect(container.services.has('DefaultService')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolve - singleton', () => {
|
||||
test('should return the same instance for singleton', () => {
|
||||
container.register('SingletonService', () => ({ id: Math.random() }), 'singleton');
|
||||
|
||||
const instance1 = container.resolve('SingletonService');
|
||||
const instance2 = container.resolve('SingletonService');
|
||||
|
||||
expect(instance1).toBe(instance2);
|
||||
expect(instance1.id).toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('should return the pre-created singleton instance', () => {
|
||||
const mockInstance = { id: 123, name: 'Mock' };
|
||||
container.register('PreCreatedService', () => mockInstance, 'singleton');
|
||||
|
||||
const resolved = container.resolve('PreCreatedService');
|
||||
|
||||
expect(resolved).toBe(mockInstance);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolve - transient', () => {
|
||||
test('should return different instances for transient', () => {
|
||||
container.register('TransientService', () => ({ id: Math.random() }), 'transient');
|
||||
|
||||
const instance1 = container.resolve('TransientService');
|
||||
const instance2 = container.resolve('TransientService');
|
||||
|
||||
expect(instance1).not.toBe(instance2);
|
||||
expect(instance1.id).not.toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('should call factory every time for transient', () => {
|
||||
const factory = jest.fn(() => ({ id: Math.random() }));
|
||||
container.register('TransientService', factory, 'transient');
|
||||
|
||||
container.resolve('TransientService');
|
||||
container.resolve('TransientService');
|
||||
container.resolve('TransientService');
|
||||
|
||||
expect(factory).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolve - scoped', () => {
|
||||
test('should return same instance within the same scope', () => {
|
||||
container.register('ScopedService', () => ({ id: Math.random() }), 'scoped');
|
||||
|
||||
const scope = container.createScope();
|
||||
const instance1 = scope.resolve('ScopedService');
|
||||
const instance2 = scope.resolve('ScopedService');
|
||||
|
||||
expect(instance1).toBe(instance2);
|
||||
expect(instance1.id).toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('should return different instances for different scopes', () => {
|
||||
container.register('ScopedService', () => ({ id: Math.random() }), 'scoped');
|
||||
|
||||
const scope1 = container.createScope();
|
||||
const scope2 = container.createScope();
|
||||
|
||||
const instance1 = scope1.resolve('ScopedService');
|
||||
const instance2 = scope2.resolve('ScopedService');
|
||||
|
||||
expect(instance1).not.toBe(instance2);
|
||||
expect(instance1.id).not.toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('should resolve scoped service with scope parameter', () => {
|
||||
const scopeMap = new Map();
|
||||
container.register('ScopedService', () => ({ id: Math.random() }), 'scoped');
|
||||
|
||||
const instance1 = container.resolve('ScopedService', scopeMap);
|
||||
const instance2 = container.resolve('ScopedService', scopeMap);
|
||||
|
||||
expect(instance1).toBe(instance2);
|
||||
expect(scopeMap.has('ScopedService')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolve - error handling', () => {
|
||||
test('should throw error for unregistered service', () => {
|
||||
expect(() => container.resolve('NonExistentService')).toThrow(
|
||||
"Service 'NonExistentService' is not registered"
|
||||
);
|
||||
});
|
||||
|
||||
test('should provide clear error message', () => {
|
||||
try {
|
||||
container.resolve('MissingService');
|
||||
fail('Should have thrown error');
|
||||
} catch (error) {
|
||||
expect(error.message).toContain('MissingService');
|
||||
expect(error.message).toContain('not registered');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createScope', () => {
|
||||
test('should create a scope with resolve method', () => {
|
||||
const scope = container.createScope();
|
||||
|
||||
expect(scope).toHaveProperty('resolve');
|
||||
expect(typeof scope.resolve).toBe('function');
|
||||
});
|
||||
|
||||
test('should create independent scopes', () => {
|
||||
container.register('ScopedService', () => ({ id: Math.random() }), 'scoped');
|
||||
|
||||
const scope1 = container.createScope();
|
||||
const scope2 = container.createScope();
|
||||
|
||||
const instance1 = scope1.resolve('ScopedService');
|
||||
const instance2 = scope2.resolve('ScopedService');
|
||||
|
||||
expect(instance1.id).not.toBe(instance2.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('real-world scenarios', () => {
|
||||
test('should handle database connection as singleton', () => {
|
||||
class DatabaseConnection {
|
||||
constructor() {
|
||||
this.id = Math.random();
|
||||
this.connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
container.register('Database', () => new DatabaseConnection(), 'singleton');
|
||||
|
||||
const db1 = container.resolve('Database');
|
||||
const db2 = container.resolve('Database');
|
||||
|
||||
expect(db1).toBe(db2);
|
||||
expect(db1.id).toBe(db2.id);
|
||||
expect(db1.connected).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle logger as transient', () => {
|
||||
class Logger {
|
||||
constructor() {
|
||||
this.id = Math.random();
|
||||
}
|
||||
log(msg) {
|
||||
return `[${this.id}] ${msg}`;
|
||||
}
|
||||
}
|
||||
|
||||
container.register('Logger', () => new Logger(), 'transient');
|
||||
|
||||
const logger1 = container.resolve('Logger');
|
||||
const logger2 = container.resolve('Logger');
|
||||
|
||||
expect(logger1).not.toBe(logger2);
|
||||
expect(logger1.id).not.toBe(logger2.id);
|
||||
});
|
||||
|
||||
test('should handle request context as scoped', () => {
|
||||
class RequestContext {
|
||||
constructor() {
|
||||
this.requestId = Math.random();
|
||||
this.user = null;
|
||||
this.timestamp = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
container.register('RequestContext', () => new RequestContext(), 'scoped');
|
||||
|
||||
// Request 1
|
||||
const request1Scope = container.createScope();
|
||||
const ctx1a = request1Scope.resolve('RequestContext');
|
||||
const ctx1b = request1Scope.resolve('RequestContext');
|
||||
expect(ctx1a).toBe(ctx1b);
|
||||
|
||||
// Request 2
|
||||
const request2Scope = container.createScope();
|
||||
const ctx2 = request2Scope.resolve('RequestContext');
|
||||
expect(ctx1a).not.toBe(ctx2);
|
||||
expect(ctx1a.requestId).not.toBe(ctx2.requestId);
|
||||
});
|
||||
|
||||
test('should handle dependency chain', () => {
|
||||
class Repository {
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
}
|
||||
}
|
||||
|
||||
class Service {
|
||||
constructor(repo) {
|
||||
this.repo = repo;
|
||||
}
|
||||
}
|
||||
|
||||
const mockDb = { id: 1, connected: true };
|
||||
container.register('Database', () => mockDb, 'singleton');
|
||||
container.register('Repository', () => {
|
||||
return new Repository(container.resolve('Database'));
|
||||
}, 'singleton');
|
||||
container.register('Service', () => {
|
||||
return new Service(container.resolve('Repository'));
|
||||
}, 'singleton');
|
||||
|
||||
const service = container.resolve('Service');
|
||||
|
||||
expect(service.repo).toBeDefined();
|
||||
expect(service.repo.db).toBe(mockDb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mixed lifecycle scenarios', () => {
|
||||
test('should handle mixed singleton and transient', () => {
|
||||
container.register('Config', () => ({ port: 3000 }), 'singleton');
|
||||
container.register('Handler', () => ({
|
||||
id: Math.random(),
|
||||
config: container.resolve('Config')
|
||||
}), 'transient');
|
||||
|
||||
const handler1 = container.resolve('Handler');
|
||||
const handler2 = container.resolve('Handler');
|
||||
|
||||
// Different handlers
|
||||
expect(handler1).not.toBe(handler2);
|
||||
expect(handler1.id).not.toBe(handler2.id);
|
||||
|
||||
// But same config
|
||||
expect(handler1.config).toBe(handler2.config);
|
||||
});
|
||||
|
||||
test('should handle mixed singleton and scoped', () => {
|
||||
container.register('Database', () => ({ id: 'db' }), 'singleton');
|
||||
container.register('RequestData', () => ({
|
||||
id: Math.random()
|
||||
}), 'scoped');
|
||||
|
||||
const scope1 = container.createScope();
|
||||
const scope2 = container.createScope();
|
||||
|
||||
const data1a = scope1.resolve('RequestData');
|
||||
const data1b = scope1.resolve('RequestData');
|
||||
const data2 = scope2.resolve('RequestData');
|
||||
|
||||
expect(data1a).toBe(data1b); // Same within scope
|
||||
expect(data1a).not.toBe(data2); // Different across scopes
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
const LoginUserCommandHandler = require('../../../src/application/auth/commands/LoginUserCommandHandler');
|
||||
const LoginUserCommand = require('../../../src/application/auth/commands/LoginUserCommand');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const JwtService = require('../../../src/application/services/JwtService');
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('bcryptjs');
|
||||
jest.mock('../../../src/application/services/JwtService');
|
||||
|
||||
describe('LoginUserCommandHandler', () => {
|
||||
let handler;
|
||||
let mockPrisma;
|
||||
let mockJwtService;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock JwtService instance
|
||||
mockJwtService = {
|
||||
generateToken: jest.fn()
|
||||
};
|
||||
JwtService.mockImplementation(() => mockJwtService);
|
||||
|
||||
// Mock Prisma
|
||||
mockPrisma = {
|
||||
user: {
|
||||
findUnique: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
handler = new LoginUserCommandHandler(mockPrisma);
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('handle - success cases', () => {
|
||||
it('should login user successfully with valid credentials', async () => {
|
||||
// Arrange
|
||||
const command = new LoginUserCommand('john@example.com', 'password123');
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'hashed_password',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
});
|
||||
bcrypt.compare.mockResolvedValue(true); // Password is valid
|
||||
mockJwtService.generateToken.mockReturnValue('mock_jwt_token');
|
||||
|
||||
// Act
|
||||
const result = await handler.handle(command);
|
||||
|
||||
// Assert
|
||||
expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({
|
||||
where: { email: 'john@example.com' }
|
||||
});
|
||||
expect(bcrypt.compare).toHaveBeenCalledWith('password123', 'hashed_password');
|
||||
expect(mockJwtService.generateToken).toHaveBeenCalledWith({
|
||||
userId: 1,
|
||||
email: 'john@example.com'
|
||||
});
|
||||
expect(result.user).toEqual({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date)
|
||||
});
|
||||
expect(result.token).toBe('mock_jwt_token');
|
||||
expect(result.user.password).toBeUndefined(); // Password should not be returned
|
||||
});
|
||||
});
|
||||
|
||||
describe('handle - validation errors', () => {
|
||||
it('should throw error if email is missing', async () => {
|
||||
// Arrange
|
||||
const command = new LoginUserCommand('', 'password123');
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Email and password are required');
|
||||
});
|
||||
|
||||
it('should throw error if password is missing', async () => {
|
||||
// Arrange
|
||||
const command = new LoginUserCommand('john@example.com', '');
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Email and password are required');
|
||||
});
|
||||
|
||||
it('should throw error if user does not exist', async () => {
|
||||
// Arrange
|
||||
const command = new LoginUserCommand('nonexistent@example.com', 'password123');
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Invalid email or password');
|
||||
});
|
||||
|
||||
it('should throw error if password is incorrect', async () => {
|
||||
// Arrange
|
||||
const command = new LoginUserCommand('john@example.com', 'wrongpassword');
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue({
|
||||
id: 1,
|
||||
email: 'john@example.com',
|
||||
password: 'hashed_password'
|
||||
});
|
||||
bcrypt.compare.mockResolvedValue(false); // Password is invalid
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Invalid email or password');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,194 @@
|
||||
const RegisterUserCommandHandler = require('../../../src/application/auth/commands/RegisterUserCommandHandler');
|
||||
const RegisterUserCommand = require('../../../src/application/auth/commands/RegisterUserCommand');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const JwtService = require('../../../src/application/services/JwtService');
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('bcryptjs');
|
||||
jest.mock('../../../src/application/services/JwtService');
|
||||
|
||||
describe('RegisterUserCommandHandler', () => {
|
||||
let handler;
|
||||
let mockPrisma;
|
||||
let mockEmailService;
|
||||
let mockJwtService;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock JwtService instance
|
||||
mockJwtService = {
|
||||
generateToken: jest.fn()
|
||||
};
|
||||
JwtService.mockImplementation(() => mockJwtService);
|
||||
|
||||
// Mock Prisma
|
||||
mockPrisma = {
|
||||
user: {
|
||||
findUnique: jest.fn(),
|
||||
create: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
// Mock EmailService
|
||||
mockEmailService = {
|
||||
sendWelcomeEmail: jest.fn().mockResolvedValue(true)
|
||||
};
|
||||
|
||||
handler = new RegisterUserCommandHandler(mockPrisma, mockEmailService);
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('handle - success cases', () => {
|
||||
it('should register a new user successfully', async () => {
|
||||
// Arrange
|
||||
const command = new RegisterUserCommand('John Doe', 'john@example.com', 'password123');
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue(null); // No existing user
|
||||
bcrypt.hash.mockResolvedValue('hashed_password');
|
||||
mockPrisma.user.create.mockResolvedValue({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'hashed_password',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
});
|
||||
mockJwtService.generateToken.mockReturnValue('mock_jwt_token');
|
||||
|
||||
// Act
|
||||
const result = await handler.handle(command);
|
||||
|
||||
// Assert
|
||||
expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({
|
||||
where: { email: 'john@example.com' }
|
||||
});
|
||||
expect(bcrypt.hash).toHaveBeenCalledWith('password123', 10);
|
||||
expect(mockPrisma.user.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'hashed_password'
|
||||
}
|
||||
});
|
||||
expect(mockJwtService.generateToken).toHaveBeenCalledWith({
|
||||
userId: 1,
|
||||
email: 'john@example.com'
|
||||
});
|
||||
expect(result.user).toEqual({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date)
|
||||
});
|
||||
expect(result.token).toBe('mock_jwt_token');
|
||||
expect(result.user.password).toBeUndefined(); // Password should not be returned
|
||||
});
|
||||
|
||||
it('should send welcome email after registration', async () => {
|
||||
// Arrange
|
||||
const command = new RegisterUserCommand('Jane Doe', 'jane@example.com', 'password123');
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue(null);
|
||||
bcrypt.hash.mockResolvedValue('hashed_password');
|
||||
mockPrisma.user.create.mockResolvedValue({
|
||||
id: 2,
|
||||
name: 'Jane Doe',
|
||||
email: 'jane@example.com',
|
||||
password: 'hashed_password',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
});
|
||||
mockJwtService.generateToken.mockReturnValue('mock_jwt_token');
|
||||
|
||||
// Act
|
||||
await handler.handle(command);
|
||||
|
||||
// Assert
|
||||
expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith('jane@example.com', 'Jane Doe');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handle - validation errors', () => {
|
||||
it('should throw error if name is missing', async () => {
|
||||
// Arrange
|
||||
const command = new RegisterUserCommand('', 'john@example.com', 'password123');
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Name, email and password are required');
|
||||
});
|
||||
|
||||
it('should throw error if email is missing', async () => {
|
||||
// Arrange
|
||||
const command = new RegisterUserCommand('John Doe', '', 'password123');
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Name, email and password are required');
|
||||
});
|
||||
|
||||
it('should throw error if password is missing', async () => {
|
||||
// Arrange
|
||||
const command = new RegisterUserCommand('John Doe', 'john@example.com', '');
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Name, email and password are required');
|
||||
});
|
||||
|
||||
it('should throw error if password is too short', async () => {
|
||||
// Arrange
|
||||
const command = new RegisterUserCommand('John Doe', 'john@example.com', '12345');
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Password must be at least 6 characters long');
|
||||
});
|
||||
|
||||
it('should throw error if email format is invalid', async () => {
|
||||
// Arrange
|
||||
const command = new RegisterUserCommand('John Doe', 'invalid-email', 'password123');
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Invalid email format');
|
||||
});
|
||||
|
||||
it('should throw error if user already exists', async () => {
|
||||
// Arrange
|
||||
const command = new RegisterUserCommand('John Doe', 'john@example.com', 'password123');
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue({
|
||||
id: 1,
|
||||
email: 'john@example.com'
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('User with this email already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handle - error handling', () => {
|
||||
it('should not fail if email service throws error', async () => {
|
||||
// Arrange
|
||||
const command = new RegisterUserCommand('John Doe', 'john@example.com', 'password123');
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue(null);
|
||||
bcrypt.hash.mockResolvedValue('hashed_password');
|
||||
mockPrisma.user.create.mockResolvedValue({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'hashed_password',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
});
|
||||
mockJwtService.generateToken.mockReturnValue('mock_jwt_token');
|
||||
mockEmailService.sendWelcomeEmail.mockRejectedValue(new Error('Email service error'));
|
||||
|
||||
// Act
|
||||
const result = await handler.handle(command);
|
||||
|
||||
// Assert - should still return user and token even if email fails
|
||||
expect(result.user.email).toBe('john@example.com');
|
||||
expect(result.token).toBe('mock_jwt_token');
|
||||
});
|
||||
});
|
||||
});
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
const UpdateUserProfileCommandHandler = require('../../../src/application/user/commands/UpdateUserProfileCommandHandler');
|
||||
const UpdateUserProfileCommand = require('../../../src/application/user/commands/UpdateUserProfileCommand');
|
||||
|
||||
describe('UpdateUserProfileCommandHandler', () => {
|
||||
let handler;
|
||||
let mockPrisma;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock Prisma
|
||||
mockPrisma = {
|
||||
user: {
|
||||
update: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
handler = new UpdateUserProfileCommandHandler(mockPrisma);
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('handle - success cases', () => {
|
||||
it('should update user profile successfully', async () => {
|
||||
// Arrange
|
||||
const command = new UpdateUserProfileCommand(1, 'Jane Updated');
|
||||
|
||||
mockPrisma.user.update.mockResolvedValue({
|
||||
id: 1,
|
||||
name: 'Jane Updated',
|
||||
email: 'jane@example.com',
|
||||
password: 'hashed_password',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await handler.handle(command);
|
||||
|
||||
// Assert
|
||||
expect(mockPrisma.user.update).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { name: 'Jane Updated' }
|
||||
});
|
||||
expect(result).toEqual({
|
||||
id: 1,
|
||||
name: 'Jane Updated',
|
||||
email: 'jane@example.com',
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date)
|
||||
});
|
||||
expect(result.password).toBeUndefined(); // Password should not be returned
|
||||
});
|
||||
});
|
||||
|
||||
describe('handle - validation errors', () => {
|
||||
it('should throw error if name is missing', async () => {
|
||||
// Arrange
|
||||
const command = new UpdateUserProfileCommand(1, '');
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Name is required');
|
||||
});
|
||||
|
||||
it('should throw error if name is null', async () => {
|
||||
// Arrange
|
||||
const command = new UpdateUserProfileCommand(1, null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Name is required');
|
||||
});
|
||||
|
||||
it('should throw error if name is undefined', async () => {
|
||||
// Arrange
|
||||
const command = new UpdateUserProfileCommand(1, undefined);
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(command)).rejects.toThrow('Name is required');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,194 @@
|
||||
const AuthController = require('../../../src/api/controllers/AuthController');
|
||||
const RegisterUserCommand = require('../../../src/application/auth/commands/RegisterUserCommand');
|
||||
const LoginUserCommand = require('../../../src/application/auth/commands/LoginUserCommand');
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller;
|
||||
let mockRegisterHandler;
|
||||
let mockLoginHandler;
|
||||
let mockReq;
|
||||
let mockRes;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock handlers
|
||||
mockRegisterHandler = {
|
||||
handle: jest.fn()
|
||||
};
|
||||
mockLoginHandler = {
|
||||
handle: jest.fn()
|
||||
};
|
||||
|
||||
controller = new AuthController(mockRegisterHandler, mockLoginHandler);
|
||||
|
||||
// Mock Express req/res
|
||||
mockReq = {
|
||||
body: {}
|
||||
};
|
||||
mockRes = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn()
|
||||
};
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should register user successfully and return 201', async () => {
|
||||
// Arrange
|
||||
mockReq.body = {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'password123'
|
||||
};
|
||||
|
||||
const mockResult = {
|
||||
user: { id: 1, name: 'John Doe', email: 'john@example.com' },
|
||||
token: 'mock_jwt_token'
|
||||
};
|
||||
|
||||
mockRegisterHandler.handle.mockResolvedValue(mockResult);
|
||||
|
||||
// Act
|
||||
await controller.register(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRegisterHandler.handle).toHaveBeenCalledWith(
|
||||
expect.any(RegisterUserCommand)
|
||||
);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
message: 'User registered successfully',
|
||||
data: mockResult
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 for validation errors', async () => {
|
||||
// Arrange
|
||||
mockReq.body = {
|
||||
name: '',
|
||||
email: 'john@example.com',
|
||||
password: 'password123'
|
||||
};
|
||||
|
||||
mockRegisterHandler.handle.mockRejectedValue(new Error('Name, email and password are required'));
|
||||
|
||||
// Act
|
||||
await controller.register(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'Name, email and password are required'
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 if user already exists', async () => {
|
||||
// Arrange
|
||||
mockReq.body = {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'password123'
|
||||
};
|
||||
|
||||
mockRegisterHandler.handle.mockRejectedValue(new Error('User with this email already exists'));
|
||||
|
||||
// Act
|
||||
await controller.register(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'User with this email already exists'
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 500 for unexpected errors', async () => {
|
||||
// Arrange
|
||||
mockReq.body = {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'password123'
|
||||
};
|
||||
|
||||
mockRegisterHandler.handle.mockRejectedValue(new Error('Database connection failed'));
|
||||
|
||||
// Act
|
||||
await controller.register(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(500);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'Database connection failed'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('should login user successfully and return 200', async () => {
|
||||
// Arrange
|
||||
mockReq.body = {
|
||||
email: 'john@example.com',
|
||||
password: 'password123'
|
||||
};
|
||||
|
||||
const mockResult = {
|
||||
user: { id: 1, name: 'John Doe', email: 'john@example.com' },
|
||||
token: 'mock_jwt_token'
|
||||
};
|
||||
|
||||
mockLoginHandler.handle.mockResolvedValue(mockResult);
|
||||
|
||||
// Act
|
||||
await controller.login(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockLoginHandler.handle).toHaveBeenCalledWith(
|
||||
expect.any(LoginUserCommand)
|
||||
);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
message: 'Login successful',
|
||||
data: mockResult
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 401 for invalid credentials', async () => {
|
||||
// Arrange
|
||||
mockReq.body = {
|
||||
email: 'john@example.com',
|
||||
password: 'wrongpassword'
|
||||
};
|
||||
|
||||
mockLoginHandler.handle.mockRejectedValue(new Error('Invalid email or password'));
|
||||
|
||||
// Act
|
||||
await controller.login(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(401);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 401 for missing credentials', async () => {
|
||||
// Arrange
|
||||
mockReq.body = {
|
||||
email: '',
|
||||
password: ''
|
||||
};
|
||||
|
||||
mockLoginHandler.handle.mockRejectedValue(new Error('Email and password are required'));
|
||||
|
||||
// Act
|
||||
await controller.login(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(401);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'Email and password are required'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,223 @@
|
||||
const UserController = require('../../../src/api/controllers/UserController');
|
||||
const GetMeQuery = require('../../../src/application/user/queries/GetMeQuery');
|
||||
const GetAllUsersQuery = require('../../../src/application/user/queries/GetAllUsersQuery');
|
||||
const GetUserByIdQuery = require('../../../src/application/user/queries/GetUserByIdQuery');
|
||||
const UpdateUserProfileCommand = require('../../../src/application/user/commands/UpdateUserProfileCommand');
|
||||
|
||||
describe('UserController', () => {
|
||||
let controller;
|
||||
let mockGetMeHandler;
|
||||
let mockGetAllUsersHandler;
|
||||
let mockGetUserByIdHandler;
|
||||
let mockUpdateProfileHandler;
|
||||
let mockReq;
|
||||
let mockRes;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock handlers
|
||||
mockGetMeHandler = { handle: jest.fn() };
|
||||
mockGetAllUsersHandler = { handle: jest.fn() };
|
||||
mockGetUserByIdHandler = { handle: jest.fn() };
|
||||
mockUpdateProfileHandler = { handle: jest.fn() };
|
||||
|
||||
controller = new UserController(
|
||||
mockGetMeHandler,
|
||||
mockGetAllUsersHandler,
|
||||
mockGetUserByIdHandler,
|
||||
mockUpdateProfileHandler
|
||||
);
|
||||
|
||||
// Mock Express req/res
|
||||
mockReq = {
|
||||
user: { userId: 1 }, // Set by authMiddleware
|
||||
body: {},
|
||||
params: {}
|
||||
};
|
||||
mockRes = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn()
|
||||
};
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getMe', () => {
|
||||
it('should return current user successfully', async () => {
|
||||
// Arrange
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com'
|
||||
};
|
||||
|
||||
mockGetMeHandler.handle.mockResolvedValue(mockUser);
|
||||
|
||||
// Act
|
||||
await controller.getMe(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockGetMeHandler.handle).toHaveBeenCalledWith(
|
||||
expect.any(GetMeQuery)
|
||||
);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
message: 'User retrieved successfully',
|
||||
data: mockUser
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 404 if user not found', async () => {
|
||||
// Arrange
|
||||
mockGetMeHandler.handle.mockRejectedValue(new Error('User not found'));
|
||||
|
||||
// Act
|
||||
await controller.getMe(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(404);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'User not found'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('should return all users successfully', async () => {
|
||||
// Arrange
|
||||
const mockUsers = [
|
||||
{ id: 1, name: 'John Doe', email: 'john@example.com' },
|
||||
{ id: 2, name: 'Jane Doe', email: 'jane@example.com' }
|
||||
];
|
||||
|
||||
mockGetAllUsersHandler.handle.mockResolvedValue(mockUsers);
|
||||
|
||||
// Act
|
||||
await controller.getAll(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockGetAllUsersHandler.handle).toHaveBeenCalledWith(
|
||||
expect.any(GetAllUsersQuery)
|
||||
);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
message: 'Users retrieved successfully',
|
||||
data: mockUsers,
|
||||
count: 2
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty array if no users exist', async () => {
|
||||
// Arrange
|
||||
mockGetAllUsersHandler.handle.mockResolvedValue([]);
|
||||
|
||||
// Act
|
||||
await controller.getAll(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
message: 'Users retrieved successfully',
|
||||
data: [],
|
||||
count: 0
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getById', () => {
|
||||
it('should return user by ID successfully', async () => {
|
||||
// Arrange
|
||||
mockReq.params = { id: '2' };
|
||||
const mockUser = {
|
||||
id: 2,
|
||||
name: 'Jane Doe',
|
||||
email: 'jane@example.com'
|
||||
};
|
||||
|
||||
mockGetUserByIdHandler.handle.mockResolvedValue(mockUser);
|
||||
|
||||
// Act
|
||||
await controller.getById(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockGetUserByIdHandler.handle).toHaveBeenCalledWith(
|
||||
expect.any(GetUserByIdQuery)
|
||||
);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
message: 'User retrieved successfully',
|
||||
data: mockUser
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 for invalid user ID', async () => {
|
||||
// Arrange
|
||||
mockReq.params = { id: 'invalid' };
|
||||
|
||||
// Act
|
||||
await controller.getById(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'Invalid user ID'
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 404 if user not found', async () => {
|
||||
// Arrange
|
||||
mockReq.params = { id: '999' };
|
||||
mockGetUserByIdHandler.handle.mockRejectedValue(new Error('User not found'));
|
||||
|
||||
// Act
|
||||
await controller.getById(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(404);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'User not found'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateMe', () => {
|
||||
it('should update user profile successfully', async () => {
|
||||
// Arrange
|
||||
mockReq.body = { name: 'John Updated' };
|
||||
const mockUpdatedUser = {
|
||||
id: 1,
|
||||
name: 'John Updated',
|
||||
email: 'john@example.com'
|
||||
};
|
||||
|
||||
mockUpdateProfileHandler.handle.mockResolvedValue(mockUpdatedUser);
|
||||
|
||||
// Act
|
||||
await controller.updateMe(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockUpdateProfileHandler.handle).toHaveBeenCalledWith(
|
||||
expect.any(UpdateUserProfileCommand)
|
||||
);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
message: 'Profile updated successfully',
|
||||
data: mockUpdatedUser
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 if name is missing', async () => {
|
||||
// Arrange
|
||||
mockReq.body = { name: '' };
|
||||
mockUpdateProfileHandler.handle.mockRejectedValue(new Error('Name is required'));
|
||||
|
||||
// Act
|
||||
await controller.updateMe(mockReq, mockRes);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'Name is required'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
const authMiddleware = require('../../../src/api/middlewares/authMiddleware');
|
||||
const JwtService = require('../../../src/application/services/JwtService');
|
||||
|
||||
// Mock JwtService
|
||||
jest.mock('../../../src/application/services/JwtService');
|
||||
|
||||
describe('authMiddleware (Cookie-based)', () => {
|
||||
let mockReq;
|
||||
let mockRes;
|
||||
let mockNext;
|
||||
let mockJwtService;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock Express req/res/next
|
||||
mockReq = {
|
||||
cookies: {}
|
||||
};
|
||||
mockRes = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn()
|
||||
};
|
||||
mockNext = jest.fn();
|
||||
|
||||
// Mock JwtService instance
|
||||
mockJwtService = {
|
||||
extractTokenFromCookies: jest.fn(),
|
||||
verifyToken: jest.fn()
|
||||
};
|
||||
|
||||
JwtService.mockImplementation(() => mockJwtService);
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('successful authentication', () => {
|
||||
it('should authenticate valid JWT token from cookie and call next()', () => {
|
||||
// Arrange
|
||||
mockReq.cookies = { auth_token: 'valid_token_123' };
|
||||
|
||||
const mockDecoded = {
|
||||
userId: 1,
|
||||
email: 'john@example.com'
|
||||
};
|
||||
|
||||
mockJwtService.extractTokenFromCookies.mockReturnValue('valid_token_123');
|
||||
mockJwtService.verifyToken.mockReturnValue(mockDecoded);
|
||||
|
||||
// Act
|
||||
authMiddleware(mockReq, mockRes, mockNext);
|
||||
|
||||
// Assert
|
||||
expect(mockReq.user).toEqual({
|
||||
userId: 1,
|
||||
email: 'john@example.com'
|
||||
});
|
||||
expect(mockNext).toHaveBeenCalled();
|
||||
expect(mockRes.status).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('authentication failures', () => {
|
||||
it('should return 401 if no cookie is present', () => {
|
||||
// Arrange
|
||||
mockReq.cookies = {};
|
||||
|
||||
mockJwtService.extractTokenFromCookies.mockReturnValue(null);
|
||||
|
||||
// Act
|
||||
authMiddleware(mockReq, mockRes, mockNext);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(401);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'Authentication required',
|
||||
message: 'No token provided in cookies'
|
||||
});
|
||||
expect(mockNext).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 401 if cookie token is invalid', () => {
|
||||
// Arrange
|
||||
mockReq.cookies = { auth_token: 'invalid_token' };
|
||||
|
||||
mockJwtService.extractTokenFromCookies.mockReturnValue('invalid_token');
|
||||
mockJwtService.verifyToken.mockImplementation(() => {
|
||||
throw new Error('Invalid or expired token');
|
||||
});
|
||||
|
||||
// Act
|
||||
authMiddleware(mockReq, mockRes, mockNext);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(401);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'Authentication failed',
|
||||
message: 'Invalid or expired token'
|
||||
});
|
||||
expect(mockNext).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 401 if token is expired', () => {
|
||||
// Arrange
|
||||
mockReq.cookies = { auth_token: 'expired_token' };
|
||||
|
||||
mockJwtService.extractTokenFromCookies.mockReturnValue('expired_token');
|
||||
mockJwtService.verifyToken.mockImplementation(() => {
|
||||
throw new Error('Token has expired');
|
||||
});
|
||||
|
||||
// Act
|
||||
authMiddleware(mockReq, mockRes, mockNext);
|
||||
|
||||
// Assert
|
||||
expect(mockRes.status).toHaveBeenCalledWith(401);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'Authentication failed',
|
||||
message: 'Token has expired'
|
||||
});
|
||||
expect(mockNext).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
const corsMiddleware = require('../../../src/api/middlewares/corsMiddleware');
|
||||
|
||||
describe('CORS Middleware', () => {
|
||||
let req, res, next;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
};
|
||||
res = {
|
||||
setHeader: jest.fn(),
|
||||
status: jest.fn().mockReturnThis(),
|
||||
end: jest.fn(),
|
||||
};
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
describe('Allowed origins', () => {
|
||||
test('should allow requests from http://localhost:3001', () => {
|
||||
req.headers.origin = 'http://localhost:3001';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'http://localhost:3001');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Credentials', 'true');
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should allow requests from http://localhost:3000', () => {
|
||||
req.headers.origin = 'http://localhost:3000';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'http://localhost:3000');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Credentials', 'true');
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should allow requests from https://myapp.com', () => {
|
||||
req.headers.origin = 'https://myapp.com';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'https://myapp.com');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Credentials', 'true');
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disallowed origins', () => {
|
||||
test('should reject requests from unknown origins', () => {
|
||||
req.headers.origin = 'http://malicious-site.com';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
expect(res.end).toHaveBeenCalledWith('CORS policy: Origin not allowed');
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should reject requests from http://evil.com', () => {
|
||||
req.headers.origin = 'http://evil.com';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
expect(res.end).toHaveBeenCalledWith('CORS policy: Origin not allowed');
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Preflight requests (OPTIONS)', () => {
|
||||
beforeEach(() => {
|
||||
req.method = 'OPTIONS';
|
||||
});
|
||||
|
||||
test('should handle OPTIONS request from allowed origin', () => {
|
||||
req.headers.origin = 'http://localhost:3001';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'http://localhost:3001');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Credentials', 'true');
|
||||
expect(res.status).toHaveBeenCalledWith(204);
|
||||
expect(res.end).toHaveBeenCalled();
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should reject OPTIONS request from disallowed origin', () => {
|
||||
req.headers.origin = 'http://hackersite.com';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
expect(res.end).toHaveBeenCalledWith('CORS policy: Origin not allowed');
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('No origin header', () => {
|
||||
test('should allow requests without origin header (same-origin)', () => {
|
||||
delete req.headers.origin;
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Headers configuration', () => {
|
||||
test('should set correct CORS headers for allowed origin', () => {
|
||||
req.headers.origin = 'http://localhost:3000';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'http://localhost:3000');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Credentials', 'true');
|
||||
});
|
||||
|
||||
test('should set method headers for OPTIONS request', () => {
|
||||
req.headers.origin = 'http://localhost:3000';
|
||||
req.method = 'OPTIONS';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world scenarios', () => {
|
||||
test('should handle POST request from allowed frontend', () => {
|
||||
req.headers.origin = 'http://localhost:3001';
|
||||
req.method = 'POST';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'http://localhost:3001');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Credentials', 'true');
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should reject DELETE request from disallowed origin', () => {
|
||||
req.headers.origin = 'http://unauthorized.com';
|
||||
req.method = 'DELETE';
|
||||
|
||||
corsMiddleware(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
const GetAllUsersQueryHandler = require('../../../src/application/user/queries/GetAllUsersQueryHandler');
|
||||
const GetAllUsersQuery = require('../../../src/application/user/queries/GetAllUsersQuery');
|
||||
|
||||
describe('GetAllUsersQueryHandler', () => {
|
||||
let handler;
|
||||
let mockPrisma;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock Prisma
|
||||
mockPrisma = {
|
||||
user: {
|
||||
findMany: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
handler = new GetAllUsersQueryHandler(mockPrisma);
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('handle - success cases', () => {
|
||||
it('should return all users successfully', async () => {
|
||||
// Arrange
|
||||
const query = new GetAllUsersQuery();
|
||||
|
||||
mockPrisma.user.findMany.mockResolvedValue([
|
||||
{
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'hashed_password_1',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Jane Doe',
|
||||
email: 'jane@example.com',
|
||||
password: 'hashed_password_2',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
]);
|
||||
|
||||
// Act
|
||||
const result = await handler.handle(query);
|
||||
|
||||
// Assert
|
||||
expect(mockPrisma.user.findMany).toHaveBeenCalledWith({
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toEqual({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date)
|
||||
});
|
||||
expect(result[1]).toEqual({
|
||||
id: 2,
|
||||
name: 'Jane Doe',
|
||||
email: 'jane@example.com',
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date)
|
||||
});
|
||||
// Passwords should not be returned
|
||||
expect(result[0].password).toBeUndefined();
|
||||
expect(result[1].password).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return empty array if no users exist', async () => {
|
||||
// Arrange
|
||||
const query = new GetAllUsersQuery();
|
||||
|
||||
mockPrisma.user.findMany.mockResolvedValue([]);
|
||||
|
||||
// Act
|
||||
const result = await handler.handle(query);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
const GetMeQueryHandler = require('../../../src/application/user/queries/GetMeQueryHandler');
|
||||
const GetMeQuery = require('../../../src/application/user/queries/GetMeQuery');
|
||||
|
||||
describe('GetMeQueryHandler', () => {
|
||||
let handler;
|
||||
let mockPrisma;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock Prisma
|
||||
mockPrisma = {
|
||||
user: {
|
||||
findUnique: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
handler = new GetMeQueryHandler(mockPrisma);
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('handle - success cases', () => {
|
||||
it('should return current user successfully', async () => {
|
||||
// Arrange
|
||||
const query = new GetMeQuery(1);
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'hashed_password',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await handler.handle(query);
|
||||
|
||||
// Assert
|
||||
expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: 1 }
|
||||
});
|
||||
expect(result).toEqual({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date)
|
||||
});
|
||||
expect(result.password).toBeUndefined(); // Password should not be returned
|
||||
});
|
||||
});
|
||||
|
||||
describe('handle - error cases', () => {
|
||||
it('should throw error if user not found', async () => {
|
||||
// Arrange
|
||||
const query = new GetMeQuery(999);
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(query)).rejects.toThrow('User not found');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
const GetUserByIdQueryHandler = require('../../../src/application/user/queries/GetUserByIdQueryHandler');
|
||||
const GetUserByIdQuery = require('../../../src/application/user/queries/GetUserByIdQuery');
|
||||
|
||||
describe('GetUserByIdQueryHandler', () => {
|
||||
let handler;
|
||||
let mockPrisma;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock Prisma
|
||||
mockPrisma = {
|
||||
user: {
|
||||
findUnique: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
handler = new GetUserByIdQueryHandler(mockPrisma);
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('handle - success cases', () => {
|
||||
it('should return user by ID successfully', async () => {
|
||||
// Arrange
|
||||
const query = new GetUserByIdQuery(1);
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
password: 'hashed_password',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await handler.handle(query);
|
||||
|
||||
// Assert
|
||||
expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: 1 }
|
||||
});
|
||||
expect(result).toEqual({
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date)
|
||||
});
|
||||
expect(result.password).toBeUndefined(); // Password should not be returned
|
||||
});
|
||||
});
|
||||
|
||||
describe('handle - validation errors', () => {
|
||||
it('should throw error if userId is invalid (NaN)', async () => {
|
||||
// Arrange
|
||||
const query = new GetUserByIdQuery('invalid');
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(query)).rejects.toThrow('Valid user ID is required');
|
||||
});
|
||||
|
||||
it('should throw error if userId is null', async () => {
|
||||
// Arrange
|
||||
const query = new GetUserByIdQuery(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(query)).rejects.toThrow('Valid user ID is required');
|
||||
});
|
||||
|
||||
it('should throw error if userId is undefined', async () => {
|
||||
// Arrange
|
||||
const query = new GetUserByIdQuery(undefined);
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(query)).rejects.toThrow('Valid user ID is required');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handle - error cases', () => {
|
||||
it('should throw error if user not found', async () => {
|
||||
// Arrange
|
||||
const query = new GetUserByIdQuery(999);
|
||||
|
||||
mockPrisma.user.findUnique.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(handler.handle(query)).rejects.toThrow('User not found');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,240 @@
|
||||
const EmailService = require('../../../src/application/services/EmailService');
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
// Mock nodemailer
|
||||
jest.mock('nodemailer');
|
||||
|
||||
describe('EmailService', () => {
|
||||
let emailService;
|
||||
let mockTransporter;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset mocks
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Mock transporter
|
||||
mockTransporter = {
|
||||
sendMail: jest.fn().mockResolvedValue({
|
||||
messageId: 'test-message-id',
|
||||
response: '250 OK',
|
||||
}),
|
||||
};
|
||||
|
||||
// Mock nodemailer.createTransport
|
||||
nodemailer.createTransport.mockReturnValue(mockTransporter);
|
||||
|
||||
// Create EmailService instance
|
||||
emailService = new EmailService();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
test('should create transporter with correct configuration', () => {
|
||||
expect(nodemailer.createTransport).toHaveBeenCalled();
|
||||
|
||||
const config = nodemailer.createTransport.mock.calls[0][0];
|
||||
expect(config).toHaveProperty('host');
|
||||
expect(config).toHaveProperty('port');
|
||||
expect(config).toHaveProperty('auth');
|
||||
expect(config.auth).toHaveProperty('user');
|
||||
expect(config.auth).toHaveProperty('pass');
|
||||
});
|
||||
|
||||
test('should initialize transporter instance', () => {
|
||||
expect(emailService.transporter).toBeDefined();
|
||||
expect(emailService.transporter).toBe(mockTransporter);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendWelcomeEmail', () => {
|
||||
test('should send welcome email with correct parameters', async () => {
|
||||
const userEmail = 'test@example.com';
|
||||
const userName = 'John Doe';
|
||||
|
||||
await emailService.sendWelcomeEmail(userEmail, userName);
|
||||
|
||||
expect(mockTransporter.sendMail).toHaveBeenCalledTimes(1);
|
||||
|
||||
const emailOptions = mockTransporter.sendMail.mock.calls[0][0];
|
||||
expect(emailOptions.to).toBe(userEmail);
|
||||
expect(emailOptions.subject).toContain('Üdvözlünk');
|
||||
expect(emailOptions).toHaveProperty('html');
|
||||
});
|
||||
|
||||
test('should include user name in email content', async () => {
|
||||
const userEmail = 'jane@example.com';
|
||||
const userName = 'Jane Smith';
|
||||
|
||||
await emailService.sendWelcomeEmail(userEmail, userName);
|
||||
|
||||
const emailOptions = mockTransporter.sendMail.mock.calls[0][0];
|
||||
expect(emailOptions.html).toContain(userName);
|
||||
});
|
||||
|
||||
test('should include user email in email content', async () => {
|
||||
const userEmail = 'contact@test.com';
|
||||
const userName = 'Test User';
|
||||
|
||||
await emailService.sendWelcomeEmail(userEmail, userName);
|
||||
|
||||
const emailOptions = mockTransporter.sendMail.mock.calls[0][0];
|
||||
expect(emailOptions.html).toContain(userEmail);
|
||||
});
|
||||
|
||||
test('should return messageId on successful send', async () => {
|
||||
const result = await emailService.sendWelcomeEmail('user@test.com', 'User');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBe('test-message-id');
|
||||
});
|
||||
|
||||
test('should use correct from address', async () => {
|
||||
await emailService.sendWelcomeEmail('recipient@test.com', 'Recipient');
|
||||
|
||||
const emailOptions = mockTransporter.sendMail.mock.calls[0][0];
|
||||
expect(emailOptions).toHaveProperty('from');
|
||||
expect(emailOptions.from).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should handle multiple recipients', async () => {
|
||||
await emailService.sendWelcomeEmail('user1@test.com', 'User 1');
|
||||
await emailService.sendWelcomeEmail('user2@test.com', 'User 2');
|
||||
await emailService.sendWelcomeEmail('user3@test.com', 'User 3');
|
||||
|
||||
expect(mockTransporter.sendMail).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
test('should throw error when email sending fails', async () => {
|
||||
mockTransporter.sendMail.mockRejectedValue(new Error('SMTP connection failed'));
|
||||
|
||||
await expect(
|
||||
emailService.sendWelcomeEmail('user@test.com', 'User')
|
||||
).rejects.toThrow('SMTP connection failed');
|
||||
});
|
||||
|
||||
test('should throw error for invalid email address', async () => {
|
||||
mockTransporter.sendMail.mockRejectedValue(new Error('Invalid recipient'));
|
||||
|
||||
await expect(
|
||||
emailService.sendWelcomeEmail('invalid-email', 'User')
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('should throw error when transporter is not configured', async () => {
|
||||
mockTransporter.sendMail.mockRejectedValue(new Error('Transporter not configured'));
|
||||
|
||||
await expect(
|
||||
emailService.sendWelcomeEmail('user@test.com', 'User')
|
||||
).rejects.toThrow('Transporter not configured');
|
||||
});
|
||||
});
|
||||
|
||||
describe('template rendering', () => {
|
||||
test('should render HTML template with Handlebars', async () => {
|
||||
await emailService.sendWelcomeEmail('template@test.com', 'Template User');
|
||||
|
||||
const emailOptions = mockTransporter.sendMail.mock.calls[0][0];
|
||||
|
||||
// Check that HTML is rendered (not raw Handlebars template)
|
||||
expect(emailOptions.html).not.toContain('{{userName}}');
|
||||
expect(emailOptions.html).not.toContain('{{userEmail}}');
|
||||
expect(emailOptions.html).toContain('Template User');
|
||||
expect(emailOptions.html).toContain('template@test.com');
|
||||
});
|
||||
|
||||
test('should handle special characters in user name', async () => {
|
||||
const specialName = "O'Reilly & Sons <script>alert('xss')</script>";
|
||||
|
||||
await emailService.sendWelcomeEmail('user@test.com', specialName);
|
||||
|
||||
const emailOptions = mockTransporter.sendMail.mock.calls[0][0];
|
||||
|
||||
// Handlebars should escape HTML by default
|
||||
expect(emailOptions.html).toBeDefined();
|
||||
});
|
||||
|
||||
test('should use correct template file path', async () => {
|
||||
await emailService.sendWelcomeEmail('user@test.com', 'User');
|
||||
|
||||
// Verify that email was sent (which means template was loaded successfully)
|
||||
expect(mockTransporter.sendMail).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('real-world scenarios', () => {
|
||||
test('should send welcome email after user registration', async () => {
|
||||
const newUser = {
|
||||
email: 'newuser@example.com',
|
||||
name: 'New User',
|
||||
};
|
||||
|
||||
const result = await emailService.sendWelcomeEmail(newUser.email, newUser.name);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(mockTransporter.sendMail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: newUser.email,
|
||||
html: expect.stringContaining(newUser.name),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should handle concurrent email sends', async () => {
|
||||
const users = [
|
||||
{ email: 'user1@test.com', name: 'User 1' },
|
||||
{ email: 'user2@test.com', name: 'User 2' },
|
||||
{ email: 'user3@test.com', name: 'User 3' },
|
||||
];
|
||||
|
||||
await Promise.all(
|
||||
users.map(user => emailService.sendWelcomeEmail(user.email, user.name))
|
||||
);
|
||||
|
||||
expect(mockTransporter.sendMail).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('should work with Ethereal Email test account', async () => {
|
||||
// Simulate Ethereal Email configuration
|
||||
const etherealTransporter = {
|
||||
sendMail: jest.fn().mockResolvedValue({
|
||||
messageId: '<ethereal-id@ethereal.email>',
|
||||
response: '250 Accepted',
|
||||
}),
|
||||
};
|
||||
|
||||
nodemailer.createTransport.mockReturnValue(etherealTransporter);
|
||||
const etherealEmailService = new EmailService();
|
||||
|
||||
const result = await etherealEmailService.sendWelcomeEmail('test@ethereal.email', 'Test User');
|
||||
|
||||
expect(result.messageId).toContain('ethereal');
|
||||
expect(etherealTransporter.sendMail).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('email content validation', () => {
|
||||
test('should include welcome message', async () => {
|
||||
await emailService.sendWelcomeEmail('user@test.com', 'User');
|
||||
|
||||
const emailOptions = mockTransporter.sendMail.mock.calls[0][0];
|
||||
expect(emailOptions.html.toLowerCase()).toMatch(/üdvözl|welcome/i);
|
||||
});
|
||||
|
||||
test('should be HTML formatted', async () => {
|
||||
await emailService.sendWelcomeEmail('user@test.com', 'User');
|
||||
|
||||
const emailOptions = mockTransporter.sendMail.mock.calls[0][0];
|
||||
expect(emailOptions.html).toContain('<html');
|
||||
expect(emailOptions.html).toContain('</html>');
|
||||
});
|
||||
|
||||
test('should have valid subject line', async () => {
|
||||
await emailService.sendWelcomeEmail('user@test.com', 'User');
|
||||
|
||||
const emailOptions = mockTransporter.sendMail.mock.calls[0][0];
|
||||
expect(emailOptions.subject).toBeTruthy();
|
||||
expect(emailOptions.subject.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
const JwtService = require('../../../src/application/services/JwtService');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
// Mock jsonwebtoken
|
||||
jest.mock('jsonwebtoken');
|
||||
|
||||
describe('JwtService', () => {
|
||||
let jwtService;
|
||||
const mockSecret = 'test-secret';
|
||||
const mockExpiresIn = '1h';
|
||||
|
||||
beforeEach(() => {
|
||||
// Setup environment variables
|
||||
process.env.JWT_SECRET = mockSecret;
|
||||
process.env.JWT_EXPIRES_IN = mockExpiresIn;
|
||||
|
||||
jwtService = new JwtService();
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('generateToken', () => {
|
||||
it('should generate a JWT token with payload', () => {
|
||||
// Arrange
|
||||
const payload = { userId: 1, email: 'john@example.com' };
|
||||
const mockToken = 'mock_jwt_token_abc123';
|
||||
|
||||
jwt.sign.mockReturnValue(mockToken);
|
||||
|
||||
// Act
|
||||
const result = jwtService.generateToken(payload);
|
||||
|
||||
// Assert
|
||||
expect(jwt.sign).toHaveBeenCalledWith(payload, mockSecret, { expiresIn: mockExpiresIn });
|
||||
expect(result).toBe(mockToken);
|
||||
});
|
||||
|
||||
it('should use default secret if JWT_SECRET not set', () => {
|
||||
// Arrange
|
||||
delete process.env.JWT_SECRET;
|
||||
jwtService = new JwtService();
|
||||
const payload = { userId: 1, email: 'john@example.com' };
|
||||
|
||||
jwt.sign.mockReturnValue('token');
|
||||
|
||||
// Act
|
||||
jwtService.generateToken(payload);
|
||||
|
||||
// Assert
|
||||
expect(jwt.sign).toHaveBeenCalledWith(payload, 'default-secret-change-me', expect.any(Object));
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyToken', () => {
|
||||
it('should verify and return decoded token', () => {
|
||||
// Arrange
|
||||
const token = 'valid_token';
|
||||
const mockDecoded = { userId: 1, email: 'john@example.com' };
|
||||
|
||||
jwt.verify.mockReturnValue(mockDecoded);
|
||||
|
||||
// Act
|
||||
const result = jwtService.verifyToken(token);
|
||||
|
||||
// Assert
|
||||
expect(jwt.verify).toHaveBeenCalledWith(token, mockSecret);
|
||||
expect(result).toEqual(mockDecoded);
|
||||
});
|
||||
|
||||
it('should throw error for invalid token', () => {
|
||||
// Arrange
|
||||
const token = 'invalid_token';
|
||||
|
||||
jwt.verify.mockImplementation(() => {
|
||||
throw new Error('jwt malformed');
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
expect(() => jwtService.verifyToken(token)).toThrow('Invalid or expired token');
|
||||
});
|
||||
|
||||
it('should throw error for expired token', () => {
|
||||
// Arrange
|
||||
const token = 'expired_token';
|
||||
|
||||
jwt.verify.mockImplementation(() => {
|
||||
throw new Error('jwt expired');
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
expect(() => jwtService.verifyToken(token)).toThrow('Invalid or expired token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractTokenFromCookies', () => {
|
||||
it('should extract token from cookies object', () => {
|
||||
// Arrange
|
||||
const cookies = { auth_token: 'abc123xyz' };
|
||||
|
||||
// Act
|
||||
const result = jwtService.extractTokenFromCookies(cookies);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('abc123xyz');
|
||||
});
|
||||
|
||||
it('should return null if cookies object is empty', () => {
|
||||
// Arrange
|
||||
const cookies = {};
|
||||
|
||||
// Act
|
||||
const result = jwtService.extractTokenFromCookies(cookies);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null if cookies is null', () => {
|
||||
// Arrange
|
||||
const cookies = null;
|
||||
|
||||
// Act
|
||||
const result = jwtService.extractTokenFromCookies(cookies);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null if cookies is undefined', () => {
|
||||
// Arrange
|
||||
const cookies = undefined;
|
||||
|
||||
// Act
|
||||
const result = jwtService.extractTokenFromCookies(cookies);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCookieOptions', () => {
|
||||
it('should return secure cookie options in production', () => {
|
||||
// Arrange
|
||||
process.env.NODE_ENV = 'production';
|
||||
jwtService = new JwtService();
|
||||
|
||||
// Act
|
||||
const options = jwtService.getCookieOptions();
|
||||
|
||||
// Assert
|
||||
expect(options.httpOnly).toBe(true);
|
||||
expect(options.secure).toBe(true);
|
||||
expect(options.sameSite).toBe('strict');
|
||||
expect(options.path).toBe('/');
|
||||
expect(options.maxAge).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should return non-secure cookie options in development', () => {
|
||||
// Arrange
|
||||
process.env.NODE_ENV = 'development';
|
||||
jwtService = new JwtService();
|
||||
|
||||
// Act
|
||||
const options = jwtService.getCookieOptions();
|
||||
|
||||
// Assert
|
||||
expect(options.httpOnly).toBe(true);
|
||||
expect(options.secure).toBe(false);
|
||||
expect(options.sameSite).toBe('strict');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCookieName', () => {
|
||||
it('should return the cookie name', () => {
|
||||
// Act
|
||||
const name = jwtService.getCookieName();
|
||||
|
||||
// Assert
|
||||
expect(name).toBe('auth_token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractTokenFromHeader (legacy)', () => {
|
||||
it('should extract token from valid Authorization header', () => {
|
||||
// Arrange
|
||||
const authHeader = 'Bearer abc123xyz';
|
||||
|
||||
// Act
|
||||
const result = jwtService.extractTokenFromHeader(authHeader);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('abc123xyz');
|
||||
});
|
||||
|
||||
it('should return null if Authorization header is missing', () => {
|
||||
// Arrange
|
||||
const authHeader = null;
|
||||
|
||||
// Act
|
||||
const result = jwtService.extractTokenFromHeader(authHeader);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null if Authorization header does not start with Bearer', () => {
|
||||
// Arrange
|
||||
const authHeader = 'Basic abc123xyz';
|
||||
|
||||
// Act
|
||||
const result = jwtService.extractTokenFromHeader(authHeader);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
const { expect } = require('chai');
|
||||
const UserService = require('../src/services/UserService');
|
||||
|
||||
describe('UserService', () => {
|
||||
let emailService;
|
||||
let userService;
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO 1: Hozz létre egy mock EmailService-t (sendWelcomeEmail = async () => {})
|
||||
// Tipp: emailService = { sendWelcomeEmail: async (email, name) => { /* mock */ } };
|
||||
|
||||
// TODO 2: Példányosítsd a UserService-t a mock-kal
|
||||
// Tipp: userService = new UserService(emailService);
|
||||
});
|
||||
|
||||
it('should create a user and send welcome email', async () => {
|
||||
// TODO 3: Teszt: createUser létrehoz egy usert és email-t küld
|
||||
// 1. Hívd meg a createUser-t: const user = await userService.createUser('Test User', 'test@example.com');
|
||||
// 2. Ellenőrizd az user property-ket: expect(user).to.have.property('id');
|
||||
// 3. Ellenőrizd a name-et: expect(user.name).to.equal('Test User');
|
||||
// 4. Ellenőrizd, hogy a user a listában van: const users = userService.getAllUsers();
|
||||
});
|
||||
|
||||
it('should throw error for duplicate email', async () => {
|
||||
// TODO 4: Teszt: duplikált email hibát dob
|
||||
// 1. Hozz létre egy usert: await userService.createUser('User 1', 'test@example.com');
|
||||
// 2. Próbáld létrehozni ugyanazzal az emaillel: expect(...).to.throw() vagy try-catch
|
||||
// Tipp async esetén: try { await userService.createUser(...); } catch(e) { expect(e.message).to.include('already exists'); }
|
||||
});
|
||||
|
||||
it('should validate required fields', async () => {
|
||||
// TODO 5: Teszt: hiányzó mezők validációs hibát dobnak
|
||||
// 1. Próbáld meg név nélkül: expect(() => userService.createUser('', 'test@example.com')).to.throw();
|
||||
// 2. Próbáld meg email nélkül: expect(() => userService.createUser('Test', '')).to.throw();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user