Add unit tests for models and services

- Implemented unit tests for the Package model, covering methods such as generateId, findById, findByName, and version management.
- Created unit tests for the Repository model, including repository creation, name validation, and retrieval methods.
- Added tests for the Session model, focusing on session creation, validation, and invalidation.
- Developed unit tests for the User model, ensuring user creation, password hashing, and retrieval methods function correctly.
- Implemented AuthService tests, validating login, token refresh, and session management.
- Added TokenService tests, covering token creation, validation, and revocation processes.
This commit is contained in:
2025-11-28 15:27:04 +00:00
parent 61324ba195
commit 44e92d48f2
50 changed files with 4403 additions and 108 deletions

View File

@@ -11,7 +11,7 @@ import { ToastService } from '../../core/services/toast.service';
<div class="p-6 max-w-7xl mx-auto">
@if (loading()) {
<div class="flex items-center justify-center py-12">
<svg class="animate-spin h-8 w-8 text-primary-600" fill="none" viewBox="0 0 24 24">
<svg class="animate-spin h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
@@ -20,33 +20,36 @@ import { ToastService } from '../../core/services/toast.service';
<!-- Header -->
<div class="flex items-start justify-between mb-8">
<div class="flex items-center gap-4">
<div class="w-16 h-16 bg-gray-200 dark:bg-gray-700 rounded-xl flex items-center justify-center">
<span class="text-2xl font-medium text-gray-600 dark:text-gray-300">
<div class="w-16 h-16 bg-muted flex items-center justify-center">
<span class="font-mono text-2xl font-medium text-muted-foreground">
{{ organization()!.name.charAt(0).toUpperCase() }}
</span>
</div>
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ organization()!.displayName }}</h1>
<p class="text-gray-500 dark:text-gray-400">&#64;{{ organization()!.name }}</p>
<h1 class="font-mono text-2xl font-bold text-foreground">{{ organization()!.displayName }}</h1>
<p class="font-mono text-muted-foreground">&#64;{{ organization()!.name }}</p>
</div>
</div>
<div class="flex items-center gap-3">
@if (organization()!.isPublic) {
<span class="badge-default">Public</span>
<span class="badge-accent">Public</span>
} @else {
<span class="badge-warning">Private</span>
<span class="badge-primary">Private</span>
}
</div>
</div>
@if (organization()!.description) {
<p class="text-gray-600 dark:text-gray-400 mb-8">{{ organization()!.description }}</p>
<p class="font-mono text-muted-foreground mb-8">{{ organization()!.description }}</p>
}
<!-- Repositories Section -->
<div class="mb-8">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Repositories</h2>
<div class="section-header">
<div class="section-indicator"></div>
<span class="section-label">Repositories</span>
</div>
<button class="btn-primary btn-sm">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
@@ -57,26 +60,26 @@ import { ToastService } from '../../core/services/toast.service';
@if (repositories().length === 0) {
<div class="card card-content text-center py-8">
<p class="text-gray-500 dark:text-gray-400">No repositories yet</p>
<p class="font-mono text-muted-foreground">No repositories yet</p>
</div>
} @else {
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@for (repo of repositories(); track repo.id) {
<a [routerLink]="['repositories', repo.id]" class="card card-content hover:border-primary-300 dark:hover:border-primary-700 transition-colors">
<a [routerLink]="['repositories', repo.id]" class="card card-content hover:border-primary/50 transition-colors">
<div class="flex items-start justify-between">
<div>
<h3 class="font-medium text-gray-900 dark:text-gray-100">{{ repo.displayName }}</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ repo.name }}</p>
<h3 class="font-mono font-medium text-foreground">{{ repo.displayName }}</h3>
<p class="font-mono text-sm text-muted-foreground">{{ repo.name }}</p>
</div>
@if (repo.isPublic) {
<span class="badge-default">Public</span>
<span class="badge-accent">Public</span>
}
</div>
@if (repo.description) {
<p class="text-sm text-gray-600 dark:text-gray-400 mt-2 line-clamp-2">{{ repo.description }}</p>
<p class="font-mono text-sm text-muted-foreground mt-2 line-clamp-2">{{ repo.description }}</p>
}
<div class="mt-3 flex items-center gap-4">
<div class="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
<div class="flex items-center gap-1 font-mono text-sm text-muted-foreground">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
@@ -95,18 +98,24 @@ import { ToastService } from '../../core/services/toast.service';
</div>
<!-- Stats -->
<div class="mb-4">
<div class="section-header">
<div class="section-indicator"></div>
<span class="section-label">Statistics</span>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="card card-content">
<p class="text-sm text-gray-500 dark:text-gray-400">Members</p>
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ organization()!.memberCount }}</p>
<p class="font-mono text-sm text-muted-foreground">Members</p>
<p class="font-mono text-2xl font-bold text-foreground">{{ organization()!.memberCount }}</p>
</div>
<div class="card card-content">
<p class="text-sm text-gray-500 dark:text-gray-400">Repositories</p>
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ repositories().length }}</p>
<p class="font-mono text-sm text-muted-foreground">Repositories</p>
<p class="font-mono text-2xl font-bold text-foreground">{{ repositories().length }}</p>
</div>
<div class="card card-content">
<p class="text-sm text-gray-500 dark:text-gray-400">Created</p>
<p class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ formatDate(organization()!.createdAt) }}</p>
<p class="font-mono text-sm text-muted-foreground">Created</p>
<p class="font-mono text-2xl font-bold text-foreground">{{ formatDate(organization()!.createdAt) }}</p>
</div>
</div>
}
@@ -123,18 +132,18 @@ export class OrganizationDetailComponent implements OnInit {
loading = signal(true);
ngOnInit(): void {
const orgId = this.route.snapshot.paramMap.get('orgId');
if (orgId) {
this.loadData(orgId);
const orgName = this.route.snapshot.paramMap.get('orgName');
if (orgName) {
this.loadData(orgName);
}
}
private async loadData(orgId: string): Promise<void> {
private async loadData(orgName: string): Promise<void> {
this.loading.set(true);
try {
const [org, reposResponse] = await Promise.all([
this.apiService.getOrganization(orgId).toPromise(),
this.apiService.getRepositories(orgId).toPromise(),
this.apiService.getOrganization(orgName).toPromise(),
this.apiService.getRepositories(orgName).toPromise(),
]);
this.organization.set(org || null);
this.repositories.set(reposResponse?.repositories || []);