Skip to content
This repository was archived by the owner on Nov 23, 2025. It is now read-only.
Merged

Dev #11

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions admin-service/TEST_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Admin Service - Test Guide

## Test Summary

**Total Tests: 98**
**Status: ✅ All Passing (100%)**

## Running Tests

### Run All Tests
```bash
mvn test
```

### Run Specific Test Suite
```bash
# Repository tests
mvn test -Dtest=*RepositoryTest

# Service tests
mvn test -Dtest=*ServiceTest

# Controller tests
mvn test -Dtest=*ControllerIntegrationTest

# Integration tests
mvn test -Dtest=*IntegrationTest
```

### Run Single Test Class
```bash
mvn test -Dtest=ServiceTypeRepositoryTest
```

## Test Coverage

### Repository Layer (32 tests) ✅ 100% Coverage
- `AuditLogRepositoryTest` - 5 tests
- `ReportRepositoryTest` - 6 tests
- `ServiceTypeRepositoryTest` - 7 tests
- `SystemConfigurationRepositoryTest` - 7 tests
- `ReportScheduleRepositoryTest` - 7 tests ⭐ NEW

### Service Layer (37 tests)
- `AdminServiceConfigServiceTest` - 9 tests
- `AuditLogServiceTest` - 7 tests
- `SystemConfigurationServiceTest` - 9 tests
- `AdminReportServiceTest` - 4 tests ⭐ NEW
- `AdminUserServiceTest` - 5 tests ⭐ NEW
- `AnalyticsServiceTest` - 3 tests ⭐ NEW

### Controller Layer (19 tests) ✅ 100% Coverage
- `AdminServiceConfigControllerIntegrationTest` - 4 tests
- `AuditLogControllerIntegrationTest` - 2 tests
- `SystemConfigurationControllerIntegrationTest` - 4 tests
- `AdminReportControllerIntegrationTest` - 3 tests ⭐ NEW
- `AdminUserControllerIntegrationTest` - 3 tests ⭐ NEW
- `AdminAnalyticsControllerIntegrationTest` - 3 tests ⭐ NEW
- `PublicServiceTypeControllerIntegrationTest` - 3 tests ⭐ NEW (in Public API section below)

### Integration Tests (6 tests)
- `ServiceTypeIntegrationTest` - 3 tests
- `SystemConfigurationIntegrationTest` - 3 tests

### Public API Tests (3 tests)
- `PublicServiceTypeControllerIntegrationTest` - 3 tests ⭐ NEW

### Application Test (1 test)
- `AdminServiceApplicationTests` - 1 test

**Total: 98 tests covering 100% of critical components**

## Coverage Summary

✅ **Repository Layer**: 5/5 (100%) - All repositories tested
✅ **Controller Layer**: 7/7 (100%) - All controllers tested
✅ **Service Layer**: 6/6 (100%) - All services tested
✅ **Integration Tests**: 2/2 (100%) - Full integration coverage

## Test Configuration

- **Profile**: `test`
- **Database**: H2 in-memory
- **Framework**: JUnit 5 + Mockito + Spring Test
- **Security**: Mock authentication with `@WithMockUser`

## Notes

- All tests run in isolated transactions
- Database is reset before each test
- Tests use H2 instead of PostgreSQL for speed
- Security filters are active but authentication is mocked
6 changes: 6 additions & 0 deletions admin-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<!-- PDF Generation -->
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.30</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,33 @@ private ExchangeFilterFunction jwtTokenPropagationFilter() {
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);

// Propagate Authorization header
org.springframework.web.reactive.function.client.ClientRequest.Builder requestBuilder = org.springframework.web.reactive.function.client.ClientRequest.from(clientRequest);

if (authHeader != null && authHeader.startsWith("Bearer ")) {
return Mono.just(
org.springframework.web.reactive.function.client.ClientRequest
.from(clientRequest)
.header(HttpHeaders.AUTHORIZATION, authHeader)
.build()
);
requestBuilder.header(HttpHeaders.AUTHORIZATION, authHeader);

// Also parse JWT to get claims and set X-User headers for services that rely on GatewayHeaderFilter
try {
String token = authHeader.substring(7);
// We can't easily parse JWT here without duplicating logic or dependencies.
// However, since we are already authenticated in this service, we can get details from SecurityContext
org.springframework.security.core.Authentication auth = org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
requestBuilder.header("X-User-Subject", auth.getName());

String roles = auth.getAuthorities().stream()
.map(a -> a.getAuthority().replace("ROLE_", ""))
.collect(java.util.stream.Collectors.joining(","));
requestBuilder.header("X-User-Roles", roles);
}
} catch (Exception e) {
// Ignore parsing errors, just don't set extra headers
}
}

return Mono.just(requestBuilder.build());
}
return Mono.just(clientRequest);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
Expand All @@ -27,8 +29,8 @@ public class AdminReportController {
@Operation(summary = "Generate a new on-demand report")
@PostMapping("/generate")
public ResponseEntity<ApiResponse<ReportResponse>> generateReport(
@Valid @RequestBody GenerateReportRequest request,
Authentication authentication) {
@Valid @RequestBody GenerateReportRequest request,
Authentication authentication) {
String generatedBy = authentication.getName();
ReportResponse report = adminReportService.generateReport(request, generatedBy);
return ResponseEntity.ok(ApiResponse.success("Report generation initiated", report));
Expand All @@ -37,8 +39,8 @@ public ResponseEntity<ApiResponse<ReportResponse>> generateReport(
@Operation(summary = "List all previously generated reports")
@GetMapping
public ResponseEntity<ApiResponse<List<ReportResponse>>> listGeneratedReports(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int limit) {
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int limit) {
List<ReportResponse> reports = adminReportService.getAllReports(page, limit);
return ResponseEntity.ok(ApiResponse.success("Reports retrieved successfully", reports));
}
Expand All @@ -49,4 +51,19 @@ public ResponseEntity<ApiResponse<ReportResponse>> getReportDetails(@PathVariabl
ReportResponse report = adminReportService.getReportById(reportId);
return ResponseEntity.ok(ApiResponse.success("Report retrieved successfully", report));
}

@Operation(summary = "Download a generated report")
@GetMapping("/{reportId}/download")
public ResponseEntity<byte[]> downloadReport(@PathVariable String reportId) {
ReportResponse report = adminReportService.getReportById(reportId);
byte[] data = adminReportService.downloadReport(reportId);

String filename = (report.getTitle() != null ? report.getTitle().replace(" ", "_") : "report")
+ "." + report.getFormat().toLowerCase();

return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
.body(data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.techtorque.admin_service.dto.external;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.Set;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AppointmentDto {

private String id;
private String customerId;
private String vehicleId;
private Set<String> assignedEmployeeIds;
private String assignedBayId;
private String confirmationNumber;
private String serviceType;
private LocalDateTime requestedDateTime;
private String status; // Using String to avoid enum dependency issues
private String specialInstructions;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime vehicleArrivedAt;
private String vehicleAcceptedByEmployeeId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.techtorque.admin_service.dto.external;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InvoiceDto {
private String invoiceId;
private String invoiceNumber;
private String customerId;
private String customerName;
private String customerEmail;
private String serviceId;
private String projectId;
private List<InvoiceItemDto> items;
private BigDecimal subtotal;
private BigDecimal taxAmount;
private BigDecimal discountAmount;
private BigDecimal totalAmount;
private BigDecimal paidAmount;
private BigDecimal balanceAmount;
private String status; // Using String to avoid enum dependency
private String notes;
private LocalDate dueDate;
private LocalDateTime issuedAt;
private LocalDateTime paidAt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;

// Part-payment fields
private Boolean requiresDeposit;
private BigDecimal depositAmount;
private BigDecimal depositPaid;
private LocalDateTime depositPaidAt;
private BigDecimal finalAmount;
private BigDecimal finalPaid;
private LocalDateTime finalPaidAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.techtorque.admin_service.dto.external;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InvoiceItemDto {
private String itemId;
private String description;
private Integer quantity;
private BigDecimal unitPrice;
private BigDecimal totalPrice;
private String itemType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

public interface AdminReportService {
ReportResponse generateReport(GenerateReportRequest request, String generatedBy);

List<ReportResponse> getAllReports(int page, int limit);

ReportResponse getReportById(String reportId);

byte[] downloadReport(String reportId);
}
Loading