28 Commits

Author SHA1 Message Date
f3c5401b07 added annoying bundle.yaml to gitignore
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 43s
CI / build (push) Successful in 11s
2025-05-10 22:51:59 +02:00
f2448a029f added two endpoints necessary for routing in project request phase
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-10 20:39:45 +02:00
d4533ea725 added an endpoint to see if useraccuont is pending or not
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-05-09 21:23:35 +02:00
255af7ee7f feat: final test in sharedApi passing, it took a while to find where the bug is getAppointments by project
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-07 20:57:03 +02:00
3b308cfa6d fix: my bad 403 error codes are never thrown by src code, now is up to date
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 39s
CI / build (push) Successful in 10s
2025-05-07 11:44:09 +02:00
d31bf259dd Merge branch 'main' into backend-test
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
Format / formatting (pull_request) Successful in 6s
2025-05-07 11:06:30 +02:00
43b40c9432 feat: just added 403 response
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 10s
Format / formatting (pull_request) Successful in 5s
2025-05-07 11:04:24 +02:00
e84f69c21a fix: unused imports
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-07 11:02:08 +02:00
c76e83f2bf feat: changed endpoints
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 8s
2025-05-07 11:00:15 +02:00
1f0f9196c4 feat: fixed 403 errors
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
2025-05-07 10:45:38 +02:00
40e577ef07 Merge pull request 'backend-test' (#10) from backend-test into main
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
Reviewed-on: #10
Reviewed-by: adnane <adnane.alami@bordeaux-inp.fr>
Reviewed-by: anas <anas.maillal@bordeaux-inp.fr>
Reviewed-by: omar <omar.el_alaoui_el_ismaili@bordeaux-inp.fr>
2025-05-07 10:43:30 +02:00
13845394e3 feat: added doc for endpoint make-admin
All checks were successful
Format / formatting (push) Successful in 25s
Build / build (push) Successful in 44s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 6s
2025-05-04 20:15:03 +02:00
f4589c6306 fix: bug on CORS discussed before
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 46s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 6s
2025-05-01 21:03:23 +02:00
6004bce4e8 Merge branch 'main' into backend-test
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-05-01 20:53:18 +02:00
0730275e75 feat: added create-account
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 44s
CI / build (push) Successful in 11s
2025-05-01 20:51:10 +02:00
5183a088e7 fix: forgot to remove a pram in an endpoint in docs
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 45s
CI / build (push) Successful in 13s
2025-04-29 21:41:25 +02:00
b503cae235 fix: styling
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 1m33s
CI / build (push) Successful in 11s
2025-04-29 21:25:03 +02:00
fcf4e1c01d feat: added an endpoint for fething the project an entrepreneur is associated with
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 1m28s
CI / build (push) Successful in 11s
2025-04-29 21:22:13 +02:00
3f18304028 formatting works time in and time out, empty commit to fix
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-04-29 19:31:28 +02:00
bbb4debcd8 fix: changed the data structure used in getAllSectionCells for processing the latest version of sectioneCells as it gave some exception (it doesn't like being iterated and modified at the same time)
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-04-29 19:18:12 +02:00
6f7fc70c4c didn't use the correct function for seting ids, test fixed
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 16s
2025-04-29 18:50:25 +02:00
3d57ecb01a fix: fixed some sectionCell fetching logic (previously wasn't grouping by idReference), found out that idRefrence in sectionCell is always being incremented at inserion still looking for a fix
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-04-29 13:59:05 +02:00
7e1271cfe2 Merge pull request 'fix: reverted previous commit, cache juste does not work' (#8) from fix_cache into main
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 44s
CI / build (push) Successful in 11s
Reviewed-on: #8
2025-04-06 20:56:39 +02:00
801ecb3817 Merge branch 'main' into fix_cache
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 46s
CI / build (push) Successful in 11s
Format / formatting (pull_request) Successful in 6s
2025-04-06 20:56:22 +02:00
cc89d4c79f fix: reverted previous commit, cache juste does not work
Some checks are pending
Format / formatting (push) Waiting to run
Build / build (push) Waiting to run
CI / build (push) Waiting to run
Format / formatting (pull_request) Successful in 6s
2025-04-06 20:55:35 +02:00
adf9a93e2e Merge pull request 'feat: enabled graddle cache. Subsequent actions should be fasters' (#7) from fix_cache into main
Some checks failed
CI / build (push) Waiting to run
Format / formatting (push) Successful in 5s
Build / build (push) Has been cancelled
Reviewed-on: #7
2025-04-06 20:41:13 +02:00
37d8bcc719 feat: enabled graddle cache. Subsequent actions should be fasters
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Has been cancelled
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 6s
2025-04-06 20:39:45 +02:00
ead11215ba Merge pull request 'backend-api' (#6) from backend-api into main
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 10s
Reviewed-on: #6
Reviewed-by: Theo <tlelez@enseirb-matmeca.fr>
Reviewed-by: adnane <adnane.alami@bordeaux-inp.fr>
Reviewed-by: mohamed_maoulainine <mohamed_maoulainine.maoulainine@bordeaux-inp.fr>
2025-03-26 19:04:08 +01:00
22 changed files with 3480 additions and 533 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ docker-compose.yaml
node_modules
.vscode
postgres/data
bundled.yaml

View File

@ -2,6 +2,7 @@ help:
@echo "make [clean dev-front prod dev-back dev]"
clean:
pkill -9 node
@cp config/frontdev.env front/MyINPulse-front/.env
@cp config/frontdev.env .env
@cp config/frontdev.env MyINPulse-back/.env

View File

@ -31,7 +31,7 @@ public class WebSecurityCustomConfiguration {
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(frontendUrl));
configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS"));
configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(
Arrays.asList("authorization", "content-type", "x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
@ -56,12 +56,18 @@ public class WebSecurityCustomConfiguration {
http.authorizeHttpRequests(
authorize ->
authorize
.requestMatchers("/entrepreneur/**", "/shared/**")
.requestMatchers("/entrepreneur/**")
.access(hasRole("REALM_MyINPulse-entrepreneur"))
.requestMatchers("/admin/**", "/shared/**")
.requestMatchers("/admin/**")
.access(hasRole("REALM_MyINPulse-admin"))
.requestMatchers("/shared/**")
.hasAnyRole(
"REALM_MyINPulse-admin",
"REALM_MyINPulse-entrepreneur")
.requestMatchers("/unauth/**")
.authenticated())
.authenticated()
.anyRequest()
.denyAll())
.oauth2ResourceServer(
oauth2 ->
oauth2.jwt(

View File

@ -115,4 +115,15 @@ public class AdminApi {
public Iterable<User> validateEntrepreneurAcc() {
return this.adminApiService.getPendingUsers();
}
@PostMapping("/admin/create-account")
public void createAccount(@AuthenticationPrincipal Jwt principal) {
String userSurname = principal.getClaimAsString("userSurname");
String username = principal.getClaimAsString("preferred_username");
String primaryMail = principal.getClaimAsString("email");
String secondaryMail = principal.getClaimAsString("secondaryMail");
String phoneNumber = principal.getClaimAsString("phoneNumber");
this.adminApiService.createAccount(
userSurname, username, primaryMail, secondaryMail, phoneNumber);
}
}

View File

@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@ -42,6 +43,19 @@ public class EntrepreneurApi {
sectionCellId, content, principal.getClaimAsString("email"));
}
/**
* Endpoint used to update a LC section.
*
* @return status code
*/
@GetMapping("/entrepreneur/projects")
public Iterable<Project> getEntrepreneurProjectId(
@PathVariable Long sectionCellId,
@RequestBody String content,
@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.getProjectIdViaClaim(principal.getClaimAsString("email"));
}
/**
* TODO: checkReturn Type
*
@ -81,4 +95,22 @@ public class EntrepreneurApi {
@RequestBody Project project, @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email"));
}
/*
* <p>Endpoint to check if project is has already been validated by an admin
*/
@GetMapping("/entrepreneur/projects/project-is-active")
public Boolean checkIfProjectValidated(@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.checkIfEntrepreneurProjectActive(
principal.getClaimAsString("email"));
}
/*
* <p>Endpoint to check if a user requested a project (used when project is pending)
*/
@GetMapping("/entrepreneur/projects/has-pending-request")
public Boolean checkIfHasRequested(@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.entrepreneurHasPendingRequestedProject(
principal.getClaimAsString("email"));
}
}

View File

@ -2,6 +2,7 @@ package enseirb.myinpulse.controller;
import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.service.EntrepreneurApiService;
import enseirb.myinpulse.service.UtilsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -14,13 +15,15 @@ import org.springframework.web.bind.annotation.*;
public class UnauthApi {
private final EntrepreneurApiService entrepreneurApiService;
private final UtilsService utilsService;
@Autowired
UnauthApi(EntrepreneurApiService entrepreneurApiService) {
UnauthApi(EntrepreneurApiService entrepreneurApiService, UtilsService utilsService) {
this.entrepreneurApiService = entrepreneurApiService;
this.utilsService = utilsService;
}
@GetMapping("/unauth/finalize")
@PostMapping("/unauth/finalize")
public void createAccount(@AuthenticationPrincipal Jwt principal) {
boolean sneeStatus;
if (principal.getClaimAsString("sneeStatus") != null) {
@ -46,6 +49,13 @@ public class UnauthApi {
course,
sneeStatus,
true);
entrepreneurApiService.createAccount(e);
}
@GetMapping("/unauth/check-if-not-pending")
public Boolean checkAccountStatus(@AuthenticationPrincipal Jwt principal) {
// Throws 404 if user not found
return utilsService.checkEntrepreneurNotPending(principal.getClaimAsString("email"));
}
}

View File

@ -15,4 +15,6 @@ public interface SectionCellRepository extends JpaRepository<SectionCell, Long>
Iterable<SectionCell> findByProjectSectionCellAndSectionIdAndModificationDateBefore(
Project project, long sectionId, LocalDateTime date);
Iterable<SectionCell> findByProjectSectionCell(Project project);
}

View File

@ -206,4 +206,15 @@ public class AdminApiService {
public Iterable<User> getPendingUsers() {
return this.userService.getPendingAccounts();
}
public void createAccount(
String username,
String userSurname,
String primaryMail,
String secondaryMail,
String phoneNumber) {
Administrator a =
new Administrator(username, userSurname, primaryMail, secondaryMail, phoneNumber);
this.administratorService.addAdministrator(a);
}
}

View File

@ -1,10 +1,12 @@
package enseirb.myinpulse.service;
import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING;
import static enseirb.myinpulse.model.ProjectDecisionValue.ACTIVE;
import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.SectionCell;
import enseirb.myinpulse.model.User;
import enseirb.myinpulse.service.database.*;
import org.apache.logging.log4j.LogManager;
@ -15,6 +17,8 @@ import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
public class EntrepreneurApiService {
@ -219,4 +223,62 @@ public class EntrepreneurApiService {
}
throw new ResponseStatusException(HttpStatus.CONFLICT, "User already exists in the system");
}
public Iterable<Project> getProjectIdViaClaim(String email) {
Long UserId = this.userService.getUserByEmail(email).getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(UserId);
List<Project> Project_List = new ArrayList<>();
Project_List.add(entrepreneur.getProjectParticipation());
return Project_List;
}
public Iterable<Entrepreneur> getAllEntrepreneurs() {
return entrepreneurService.getAllEntrepreneurs();
}
/**
* Checks if an entrepreneur with the given email has a project that is ACTIVE.
*
* @param email The email of the entrepreneur.
* @return true if the entrepreneur has an active project, false otherwise.
*/
public Boolean checkIfEntrepreneurProjectActive(String email) {
User user = this.userService.getUserByEmail(email);
if (user == null) {
return false;
}
Long userId = user.getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId);
if (entrepreneur == null) {
return false;
}
Project proposedProject = entrepreneur.getProjectProposed();
return proposedProject != null && proposedProject.getProjectStatus() == ACTIVE;
}
/**
* Checks if an entrepreneur with the given email has proposed a project.
*
* @param email The email of the entrepreneur.
* @return true if the entrepreneur has a proposed project, false otherwise.
*/
public Boolean entrepreneurHasPendingRequestedProject(String email) {
User user = this.userService.getUserByEmail(email);
if (user == null) {
return false;
}
Long userId = user.getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId);
if (entrepreneur == null) {
return false;
}
Project proposedProject = entrepreneur.getProjectProposed();
if (entrepreneur.getProjectProposed() == null) {
return false;
}
return proposedProject.getProjectStatus() == PENDING;
}
}

View File

@ -25,8 +25,11 @@ import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Map;
import java.util.Set;
@Service
public class SharedApiService {
@ -79,7 +82,7 @@ public class SharedApiService {
LocalDateTime dateTime = LocalDateTime.parse(date, formatter);
Project project = this.projectService.getProjectById(projectId);
return this.sectionCellService.getSectionCellsByProjectAndSectionIdBeforeDate(
return this.sectionCellService.getLatestSectionCellsByIdReferenceBeforeDate(
project, sectionId, dateTime);
}
@ -95,31 +98,36 @@ public class SharedApiService {
}
Project project = this.projectService.getProjectById(projectId);
List<SectionCell> allSectionCells = new ArrayList<SectionCell>();
project.getListSectionCell()
Map<Long, SectionCell> latestSectionCellsMap =
new HashMap<>(); // List for the intermediate result
// Iterate through all SectionCells associated with the project
// This loop iterates over project.getListSectionCell() but does NOT modify it which causes
// ConcurrentModificationException.
// Modifications are done only on the latestSectionCellsMap (which is safe).
project.getListSectionCell() // <-- Iterating over the original list (read-only)
.forEach(
projectCell -> {
AtomicBoolean sameReferenceId =
new AtomicBoolean(false); // side effect lambdas
allSectionCells.forEach(
selectedCell -> {
if (projectCell
.getIdReference()
.equals(selectedCell.getIdReference())) {
sameReferenceId.set(true);
Long idReference = projectCell.getIdReference();
// Check if we have already seen a SectionCell with this idReference in
// our map
if (latestSectionCellsMap.containsKey(idReference)) {
SectionCell existingCell = latestSectionCellsMap.get(idReference);
// Compare modification dates. If the current cell is newer, replace
// the one in the map.
if (projectCell
.getModificationDate()
.isAfter(selectedCell.getModificationDate())) {
allSectionCells.remove(selectedCell);
allSectionCells.add(projectCell);
.isAfter(existingCell.getModificationDate())) {
latestSectionCellsMap.put(idReference, projectCell);
}
} else {
// If this is the first time we encounter this idReference, add the
// cell to the map.
latestSectionCellsMap.put(idReference, projectCell);
}
});
if (!sameReferenceId.get()) {
allSectionCells.add(projectCell);
}
});
return allSectionCells;
return new ArrayList<>(latestSectionCellsMap.values());
}
// TODO: test
@ -163,18 +171,26 @@ public class SharedApiService {
"User {} tried to check the appointments related to the project {}",
mail,
projectId);
Iterable<SectionCell> sectionCells =
this.sectionCellService.getSectionCellsByProject(
projectService.getProjectById(projectId),
2L); // sectionId useless in this function ?
List<Appointment> appointments = new ArrayList<Appointment>();
sectionCells.forEach(
Project project = projectService.getProjectById(projectId);
Iterable<SectionCell> sectionCellsIterable =
this.sectionCellService.getSectionCellsByProject(project);
// Use a Set to collect unique appointments
Set<Appointment> uniqueAppointments = new HashSet<>();
sectionCellsIterable.forEach(
sectionCell -> {
appointments.addAll(
List<Appointment> sectionAppointments =
this.sectionCellService.getAppointmentsBySectionCellId(
sectionCell.getIdSectionCell()));
sectionCell.getIdSectionCell());
// Add all appointments from this section cell to the Set
uniqueAppointments.addAll(sectionAppointments);
});
return appointments;
// Convert the Set back to a List for the return value
return new ArrayList<>(uniqueAppointments);
}
public void getPDFReport(long appointmentId, String mail)

View File

@ -72,4 +72,10 @@ public class UtilsService {
return false;
}
}
public Boolean checkEntrepreneurNotPending(String email) {
// Throws 404 if user not found
User user = userService.getUserByEmail(email);
return !user.isPending();
}
}

View File

@ -14,7 +14,10 @@ import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
@ -116,6 +119,18 @@ public class SectionCellService {
return this.sectionCellRepository.findByProjectSectionCellAndSectionId(project, sectionId);
}
public Iterable<SectionCell> getSectionCellsByProject(Project project) {
logger.info("Fetching SectionCells for Project ID: {}", project.getIdProject());
Iterable<SectionCell> sectionCells =
this.sectionCellRepository.findByProjectSectionCell(project);
List<SectionCell> sectionCellList = new ArrayList<>();
sectionCells.forEach(
cell -> {
sectionCellList.add(cell);
});
return sectionCellList;
}
public Long getProjectId(Long sectionCellId) {
SectionCell sectionCell = getSectionCellById(sectionCellId);
Project sectionProject = sectionCell.getProjectSectionCell();
@ -132,4 +147,37 @@ public class SectionCellService {
return sectionCellRepository.findByProjectSectionCellAndSectionIdAndModificationDateBefore(
project, sectionId, date);
}
public Iterable<SectionCell> getLatestSectionCellsByIdReferenceBeforeDate(
Project project, long sectionId, LocalDateTime date) {
// 1. Fetch ALL relevant SectionCells modified before the date
Iterable<SectionCell> allMatchingCells =
sectionCellRepository.findByProjectSectionCellAndSectionIdAndModificationDateBefore(
project, sectionId, date);
// 2. Find the latest for each idReference
Map<Long, SectionCell> latestCellsByIdReference = new HashMap<>();
for (SectionCell cell : allMatchingCells) {
Long idReference = cell.getIdReference();
// Check if we've seen this idReference before
if (latestCellsByIdReference.containsKey(idReference)) {
// If yes, compare modification dates
SectionCell existingLatest = latestCellsByIdReference.get(idReference);
// If the current cell is more recent, update the map
if (cell.getModificationDate().isAfter(existingLatest.getModificationDate())) {
latestCellsByIdReference.put(idReference, cell);
}
} else {
// If this is the first time we see this idReference, add it to the map
latestCellsByIdReference.put(idReference, cell);
}
}
// 3. Return the collection of the latest cells (the values from the map)
return latestCellsByIdReference.values();
}
}

View File

@ -1,6 +1,6 @@
spring.application.name=myinpulse
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/${VITE_KEYCLOAK_REALM}/protocol/openid-connect/certs
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/${VITE_KEYCLOAK_REALM}
spring.datasource.url=jdbc:postgresql://${DATABASE_URL}/${BACKEND_DB}
spring.datasource.username=${BACKEND_USER}
spring.datasource.password=${BACKEND_PASSWORD}

View File

@ -8,9 +8,10 @@ import static org.mockito.Mockito.when;
import enseirb.myinpulse.model.*;
import enseirb.myinpulse.service.SharedApiService;
import enseirb.myinpulse.service.database.*;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import enseirb.myinpulse.service.UtilsService;
import com.itextpdf.text.DocumentException;
import org.junit.jupiter.api.BeforeAll; // Use BeforeAll for static setup
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; // Keep this import
@ -22,8 +23,6 @@ import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@ -123,20 +122,6 @@ public class SharedApiServiceTest {
when(mockUtilsService.isAllowedToCheckProject(eq(staticUnauthorizedMail), anyLong()))
.thenReturn(false); // Unauthorized entrepreneur NOT allowed for ANY project ID by
// default
// Add more specific mock setups here if needed for entrepreneur tests
// E.g., If you have a test specifically for an entrepreneur accessing THEIR project:
// Entrepreneur testEntrepreneur =
// entrepreneurService.addEntrepreneur(getTestEntrepreneur("specific_linked_entrepreneur"));
// Project linkedProject =
// projectService.addNewProject(getTestProject("specific_linked_project",
// staticAuthorizedAdmin));
// // Link testEntrepreneur to linkedProject in the database setup...
// when(mockUtilsService.isAllowedToCheckProject(eq(testEntrepreneur.getPrimaryMail()),
// eq(linkedProject.getIdProject()))).thenReturn(true);
// when(mockUtilsService.isAllowedToCheckProject(eq(testEntrepreneur.getPrimaryMail()),
// anyLong())).thenReturn(false); // Deny for other projects
}
// --- Helper Methods (Can remain non-static or static as needed) ---
@ -176,6 +161,17 @@ public class SharedApiServiceTest {
return sectionCell;
}
private static SectionCell getTestSectionCell(
Project project, Long sectionId, String content, LocalDateTime date, Long refrenceId) {
SectionCell sectionCell = new SectionCell();
sectionCell.setProjectSectionCell(project);
sectionCell.setSectionId(sectionId);
sectionCell.setContentSectionCell(content);
sectionCell.setModificationDate(date);
sectionCell.setIdReference(refrenceId);
return sectionCell;
}
private static Appointment getTestAppointment(
LocalDate date,
LocalTime time,
@ -307,6 +303,262 @@ public class SharedApiServiceTest {
assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode());
}
/*
* Tests retrieving section cells for a specific project and section ID before a given date
* when the user is authorized and matching cells exist.
* Verifies that only the correct cells are returned.
*/
@Test
// Commenting out failing test
void testGetSectionCells_Authorized_Found() {
Long targetSectionId = 1L;
// Set a date filter slightly in the future so our "latest before" cell is included
LocalDateTime dateFilter = LocalDateTime.now().plusMinutes(5);
// Creating versions of the SAME SectionCell (share the same idReference)
// the first version. This will get a GENERATED idReference.
SectionCell firstVersion =
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Content V1 (Oldest)",
LocalDateTime.now().minusDays(3) // Oldest date
);
sectionCellService.addNewSectionCell(firstVersion);
Long sharedIdReference = firstVersion.getIdReference();
assertNotNull(
sharedIdReference,
"idReference should be generated after saving the first version");
System.out.println("Generated sharedIdReference: " + sharedIdReference);
// Create subsequent versions and MANUALLY set the SAME idReference.
// These represent updates to the cell identified by sharedIdReference.
SectionCell middleVersion =
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Content V2 (Middle)",
LocalDateTime.now().minusDays(2), // Middle date, before filter
sharedIdReference);
middleVersion = sectionCellService.addNewSectionCell(middleVersion);
sectionCellService.updateSectionCellReferenceId(
middleVersion.getIdSectionCell(), sharedIdReference);
SectionCell latestBeforeFilter =
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Content V3 (Latest Before Filter)",
LocalDateTime.now().minusDays(1), // Latest date before filter
sharedIdReference);
latestBeforeFilter = sectionCellService.addNewSectionCell(latestBeforeFilter);
sectionCellService.updateSectionCellReferenceId(
latestBeforeFilter.getIdSectionCell(), sharedIdReference);
SectionCell futureVersion =
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Content V4 (Future - Should Be Excluded)",
LocalDateTime.now().plusDays(1), // Date is AFTER the filter
sharedIdReference);
futureVersion = sectionCellService.addNewSectionCell(futureVersion);
sectionCellService.updateSectionCellReferenceId(
futureVersion.getIdSectionCell(), sharedIdReference);
// --- Create other SectionCells that should NOT be included (different sectionId or
// project) ---
// Cell in a different section ID
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
99L, // Different sectionId
"Content in Different Section",
LocalDateTime.now()));
// Act
Iterable<SectionCell> result =
sharedApiService.getSectionCells(
staticAuthorizedProject.getIdProject(), // Use static project ID
targetSectionId,
dateFilter.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
staticAuthorizedMail); // Use static authorized mail
List<SectionCell> resultList = TestUtils.toList(result);
assertEquals(1, resultList.size());
// Verify that the returned cell is the 'latestBeforeFilter' cell
// Comparing by idSectionCell is a good way to verify the exact entity
assertEquals(
latestBeforeFilter.getIdSectionCell(),
resultList.get(0).getIdSectionCell(),
"The returned SectionCell should be the one with the latest modification date before the filter.");
// Also assert the idReference and content
assertEquals(
sharedIdReference,
resultList.get(0).getIdReference(),
"The returned cell should have the shared idReference.");
assertEquals(
"Content V3 (Latest Before Filter)",
resultList.get(0).getContentSectionCell(),
"The returned cell should have the correct content.");
}
/*
* Tests retrieving the most recent section cell for each unique idReference
* within a project when the user is authorized and cells exist.
* Verifies that only the latest version of each referenced cell is returned.
*/
// Tests getAllSectionCells
@Test
// Commenting out failing test - Removed this comment as we are fixing it
void testGetAllSectionCells_Authorized_FoundLatest() {
// Arrange: Create specific SectionCells for this test
// Define the idReference values we will use for grouping
Long refIdGroup1 = 101L;
Long refIdGroup2 = 102L;
Long refIdOtherProject = 103L;
// --- Create and Add Cells for Group 1 (refIdGroup1) ---
// Create the older cell for group 1
SectionCell tempOldCell1 =
getTestSectionCell(
staticAuthorizedProject, // Project
1L, // Section ID (assuming this groups by section within refId)
"Ref1 Old", // Name
LocalDateTime.now().minusDays(3), // Date (older)
null); // Pass null or let getTestSectionCell handle it, we'll set
// idReference later
final SectionCell oldCell1 =
sectionCellService.addNewSectionCell(tempOldCell1); // Add to DB
// Create the newer cell for group 1
SectionCell tempNewerCell1 =
getTestSectionCell(
staticAuthorizedProject, // Project
1L, // Section ID
"Ref1 Newer", // Name
LocalDateTime.now().minusDays(2), // Date (newer than oldCell1)
null); // Pass null
final SectionCell newerCell1 =
sectionCellService.addNewSectionCell(tempNewerCell1); // Add to DB
// Now, update the idReference for both cells in Group 1 to the desired value
sectionCellService.updateSectionCellReferenceId(oldCell1.getIdSectionCell(), refIdGroup1);
sectionCellService.updateSectionCellReferenceId(newerCell1.getIdSectionCell(), refIdGroup1);
// --- Create and Add Cells for Group 2 (refIdGroup2) ---
// Create the older cell for group 2
SectionCell tempOldCell2 =
getTestSectionCell(
staticAuthorizedProject, // Project
2L, // Section ID (different section)
"Ref2 Old", // Name
LocalDateTime.now().minusDays(1), // Date (older than newerCell2)
null); // Pass null
final SectionCell oldCell2 =
sectionCellService.addNewSectionCell(tempOldCell2); // Add to DB
// Create the newer cell for group 2
SectionCell tempNewerCell2 =
getTestSectionCell(
staticAuthorizedProject, // Project
2L, // Section ID
"Ref2 Newer", // Name
LocalDateTime.now(), // Date (latest)
null); // Pass null
final SectionCell newerCell2 =
sectionCellService.addNewSectionCell(tempNewerCell2); // Add to DB
// Now, update the idReference for both cells in Group 2 to the desired value
sectionCellService.updateSectionCellReferenceId(oldCell2.getIdSectionCell(), refIdGroup2);
sectionCellService.updateSectionCellReferenceId(newerCell2.getIdSectionCell(), refIdGroup2);
// --- Create and Add Cell for Other Project (refIdOtherProject) ---
Project otherProject =
projectService.addNewProject(
getTestProject(
"other_project_for_cell_test",
administratorService.addAdministrator(
getTestAdmin("other_admin_cell_test"))));
SectionCell tempOtherProjectCell =
getTestSectionCell(
otherProject, // DIFFERENT Project
1L, // Section ID
"Other Project Cell", // Name
LocalDateTime.now(), // Date
null); // Pass null
final SectionCell otherProjectCell =
sectionCellService.addNewSectionCell(tempOtherProjectCell); // Add to DB
// Now, update the idReference for the Other Project cell
sectionCellService.updateSectionCellReferenceId(
otherProjectCell.getIdSectionCell(), refIdOtherProject);
// Act
// Ensure the service call uses the correct project ID and mail
Iterable<SectionCell> result =
sharedApiService.getAllSectionCells(
staticAuthorizedProject.getIdProject(), // Use static project ID
staticAuthorizedMail); // Use static authorized mail
List<SectionCell> resultList = TestUtils.toList(result);
// Assert
// We expect 2 cells from the staticAuthorizedProject:
// - The latest one from refIdGroup1 (newerCell1)
// - The latest one from refIdGroup2 (newerCell2)
assertEquals(2, resultList.size());
// Assert that the result list contains the LATEST cell from each group within the correct
// project
assertTrue(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(newerCell1.getIdSectionCell())),
"Should contain the latest cell for Group 1"); // Add assertion message
assertTrue(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(newerCell2.getIdSectionCell())),
"Should contain the latest cell for Group 2"); // Add assertion message
// Assert that the result list does NOT contain the OLDER cells from the correct project
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(oldCell1.getIdSectionCell())),
"Should not contain the older cell for Group 1"); // Add assertion message
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(oldCell2.getIdSectionCell())),
"Should not contain the older cell for Group 2"); // Add assertion message
// Assert that the result list does NOT contain the cell from the other project
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(otherProjectCell.getIdSectionCell())),
"Should not contain cells from other projects"); // Add assertion message
}
/*
* _____ _ ____ _ ____ _ _ ____
* |_ _|__ ___| |_ / ___| ___| |_| _ \ _ __ ___ (_) ___ ___| |_| __ ) _ _
@ -459,6 +711,129 @@ public class SharedApiServiceTest {
assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode());
}
@PersistenceContext // Inject EntityManager
private EntityManager entityManager;
// Assume these static variables are defined elsewhere in your test class
// private static Project staticAuthorizedProject;
// private static String staticAuthorizedMail;
// private static Administrator staticAuthorizedAdmin;
// Assume getTestSectionCell, getTestProject, getTestAdmin, getTestAppointment, TestUtils.toList
// are defined elsewhere
@Test
void testGetAppointmentsByProjectId_Authorized_Found() {
// Arrange: Create specific SectionCells and Appointments for this test
SectionCell cell1 =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 1L, "Cell 1 Test", LocalDateTime.now()));
SectionCell cell2 =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 2L, "Cell 2 Test", LocalDateTime.now()));
Project otherProject =
projectService.addNewProject(
getTestProject(
"other_project_app_test",
administratorService.addAdministrator(
getTestAdmin("other_admin_app_test"))));
SectionCell otherProjectCell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
otherProject,
1L,
"Other Project Cell App Test",
LocalDateTime.now()));
// Create Appointments with SectionCells lists (Owning side)
Appointment app1 =
getTestAppointment(
LocalDate.now().plusDays(10),
LocalTime.NOON,
LocalTime.of(0, 30),
"Place 1 App Test",
"Subject 1 App Test",
List.of(cell1), // This links Appointment to SectionCell
null);
Appointment savedApp1 = appointmentService.addNewAppointment(app1);
Appointment app2 =
getTestAppointment(
LocalDate.now().plusDays(11),
LocalTime.NOON.plusHours(1),
LocalTime.of(1, 0),
"Place 2 App Test",
"Subject 2 App Test",
List.of(cell1, cell2), // This links Appointment to SectionCells
null);
Appointment savedApp2 = appointmentService.addNewAppointment(app2);
Appointment otherApp =
getTestAppointment(
LocalDate.now().plusDays(12),
LocalTime.MIDNIGHT,
LocalTime.of(0, 15),
"Other Place App Test",
"Other Subject App Test",
List.of(otherProjectCell), // This links Appointment to SectionCell
null);
Appointment savedOtherApp =
appointmentService.addNewAppointment(otherApp); // Capture saved entity
// --- IMPORTANT DEBUGGING STEPS ---
// Flush pending changes to the database (including join table inserts)
entityManager.flush();
// Clear the persistence context cache to ensure entities are loaded fresh from the database
entityManager.clear();
// --- END IMPORTANT DEBUGGING STEPS ---
// --- Add Debug Logging Here ---
// Re-fetch cells to see their state after saving Appointments and flushing/clearing cache
// These fetches should load from the database due to entityManager.clear()
SectionCell fetchedCell1_postPersist =
sectionCellService.getSectionCellById(cell1.getIdSectionCell());
SectionCell fetchedCell2_postPersist =
sectionCellService.getSectionCellById(cell2.getIdSectionCell());
SectionCell fetchedOtherCell_postPersist =
sectionCellService.getSectionCellById(otherProjectCell.getIdSectionCell());
// Access the lazy collections to see if they are populated from the DB
// This access should trigger lazy loading if the data is in the DB
List<Appointment> cell1Apps_postPersist =
fetchedCell1_postPersist.getAppointmentSectionCell();
List<Appointment> cell2Apps_postPersist =
fetchedCell2_postPersist.getAppointmentSectionCell();
List<Appointment> otherCellApps_postPersist =
fetchedOtherCell_postPersist.getAppointmentSectionCell();
// Ensure logging is enabled in SharedApiService and SectionCellService methods called below
Iterable<Appointment> result =
sharedApiService.getAppointmentsByProjectId(
staticAuthorizedProject.getIdProject(), // Use static project ID
staticAuthorizedMail); // Use static authorized mail
List<Appointment> resultList = TestUtils.toList(result);
// Assert
assertEquals(2, resultList.size());
assertTrue(
resultList.stream()
.anyMatch(a -> a.getIdAppointment().equals(savedApp1.getIdAppointment())));
assertTrue(
resultList.stream()
.anyMatch(a -> a.getIdAppointment().equals(savedApp2.getIdAppointment())));
assertFalse(
resultList.stream()
.anyMatch(
a ->
a.getIdAppointment()
.equals(savedOtherApp.getIdAppointment())));
}
/*
* Tests creating a new appointment request when the user is authorized
* for the project linked to the appointment's section cell.
@ -544,443 +919,4 @@ public class SharedApiServiceTest {
a.getIdAppointment()
.equals(createdAppointment.getIdAppointment())));
}
/*
* Tests creating a new appointment request when the user is not authorized
* for the project linked to the appointment's section cell.
* Verifies that an Unauthorized ResponseStatusException is thrown and the appointment is not saved.
*/
@Test
void testCreateAppointmentRequest_Unauthorized() {
// Arrange: Create transient appointment linked to a cell in the static *unauthorized*
// project
LocalDate date = LocalDate.parse("2026-01-01");
LocalTime time = LocalTime.parse("10:00:00");
LocalTime duration = LocalTime.parse("00:30:00");
String place = "Meeting Room";
String subject = "Discuss Project";
String reportContent = "Initial Report";
SectionCell linkedCell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticUnauthorizedProject,
1L,
"Related Section Content",
LocalDateTime.now()));
Report newReport = getTestReport(reportContent);
Appointment newAppointment =
getTestAppointment(
date, time, duration, place, subject, List.of(linkedCell), newReport);
// mockUtilsService is configured in BeforeEach to deny staticUnauthorizedMail for
// staticUnauthorizedProject
// Act & Assert
ResponseStatusException exception =
assertThrows(
ResponseStatusException.class,
() -> {
sharedApiService.createAppointmentRequest(
newAppointment,
staticUnauthorizedMail); // Unauthorized user mail
});
assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode());
}
/*
_____ _ _ _
| ___|_ _(_) | ___ __| |
| |_ / _` | | |/ _ \/ _` |
| _| (_| | | | __/ (_| |
|_| \__,_|_|_|\___|\__,_|
_____ _____ ____ _____
|_ _| ____/ ___|_ _|
| | | _| \___ \ | |
| | | |___ ___) || |
|_| |_____|____/ |_|
*/
/* these tests fail because of the use of mockito's eq(),
* and since thee instances are technically not the same as
* as the classes used to turn them into persistant data
* (for e.g id are set by DB) so I have to add some equal functions
* probably and look at peer tests to see what they have done but for now
* I pushed this half-human code.
*/
// --- Test Methods (Use static data from @BeforeAll where possible) ---
/*
* Tests retrieving section cells for a specific project and section ID before a given date
* when the user is authorized and matching cells exist.
* Verifies that only the correct cells are returned.
*/
/*@Test*/
// Commenting out failing test
void testGetSectionCells_Authorized_Found() {
// Arrange: Create specific SectionCells for this test scenario
Long targetSectionId = 1L;
LocalDateTime dateFilter = LocalDateTime.now().plusDays(1);
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Old Content",
LocalDateTime.now().minusDays(2)));
SectionCell recentCell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Recent Content",
LocalDateTime.now().minusDays(1)));
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 2L, "Other Section", LocalDateTime.now()));
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Future Content",
LocalDateTime.now().plusDays(2)));
// Act
Iterable<SectionCell> result =
sharedApiService.getSectionCells(
staticAuthorizedProject.getIdProject(), // Use static project ID
targetSectionId,
dateFilter.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
staticAuthorizedMail); // Use static authorized mail
List<SectionCell> resultList = TestUtils.toList(result);
// Assert
assertEquals(1, resultList.size());
assertEquals(recentCell.getIdSectionCell(), resultList.get(0).getIdSectionCell());
}
/*
* Tests retrieving the most recent section cell for each unique idReference
* within a project when the user is authorized and cells exist.
* Verifies that only the latest version of each referenced cell is returned.
*/
// Tests getAllSectionCells
/*@Test*/
// Commenting out failing test
void testGetAllSectionCells_Authorized_FoundLatest() {
// Arrange: Create specific SectionCells for this test
Long refId1 = 101L;
Long refId2 = 102L;
SectionCell tempOldCell1 =
getTestSectionCell(
staticAuthorizedProject, 1L, "Ref1 Old", LocalDateTime.now().minusDays(3));
tempOldCell1.setIdReference(refId1);
final SectionCell oldCell1 = sectionCellService.addNewSectionCell(tempOldCell1);
SectionCell tempNewerCell1 =
getTestSectionCell(
staticAuthorizedProject,
1L,
"Ref1 Newer",
LocalDateTime.now().minusDays(2));
tempNewerCell1.setIdReference(refId1);
final SectionCell newerCell1 = sectionCellService.addNewSectionCell(tempNewerCell1);
SectionCell tempOldCell2 =
getTestSectionCell(
staticAuthorizedProject, 2L, "Ref2 Old", LocalDateTime.now().minusDays(1));
tempOldCell2.setIdReference(refId2);
final SectionCell oldCell2 = sectionCellService.addNewSectionCell(tempOldCell2);
SectionCell tempNewerCell2 =
getTestSectionCell(staticAuthorizedProject, 2L, "Ref2 Newer", LocalDateTime.now());
tempNewerCell2.setIdReference(refId2);
final SectionCell newerCell2 = sectionCellService.addNewSectionCell(tempNewerCell2);
Project otherProject =
projectService.addNewProject(
getTestProject(
"other_project_for_cell_test",
administratorService.addAdministrator(
getTestAdmin("other_admin_cell_test"))));
SectionCell tempOtherProjectCell =
getTestSectionCell(otherProject, 1L, "Other Project Cell", LocalDateTime.now());
tempOtherProjectCell.setIdReference(103L);
final SectionCell otherProjectCell =
sectionCellService.addNewSectionCell(tempOtherProjectCell);
// Act
Iterable<SectionCell> result =
sharedApiService.getAllSectionCells(
staticAuthorizedProject.getIdProject(), // Use static project ID
staticAuthorizedMail); // Use static authorized mail
List<SectionCell> resultList = TestUtils.toList(result);
// Assert
assertEquals(2, resultList.size()); // Expect 2 cells (one per idReference)
assertTrue(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(newerCell1.getIdSectionCell())));
assertTrue(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(newerCell2.getIdSectionCell())));
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(oldCell1.getIdSectionCell())));
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(oldCell2.getIdSectionCell())));
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(otherProjectCell.getIdSectionCell())));
}
/*
* Tests retrieving entrepreneurs linked to a project when the user is authorized
* and entrepreneurs are linked.
* Verifies that the correct entrepreneurs are returned.
*/
// Tests getEntrepreneursByProjectId
/*@Test*/
// Commenting out failing test
void testGetEntrepreneursByProjectId_Authorized_Found() {
// Arrange: Create entrepreneur and link to static project for this test
Entrepreneur linkedEntrepreneur =
entrepreneurService.addEntrepreneur(
getTestEntrepreneur("linked_entrepreneur_test"));
// Fetch the static project to update its list
Project projectToUpdate =
projectService.getProjectById(staticAuthorizedProject.getIdProject());
projectToUpdate.updateListEntrepreneurParticipation(linkedEntrepreneur);
projectService.addNewProject(projectToUpdate); // Save the updated project
Entrepreneur otherEntrepreneur =
entrepreneurService.addEntrepreneur(getTestEntrepreneur("other_entrepreneur_test"));
// Act
Iterable<Entrepreneur> result =
sharedApiService.getEntrepreneursByProjectId(
staticAuthorizedProject.getIdProject(), staticAuthorizedMail);
List<Entrepreneur> resultList = TestUtils.toList(result);
// Assert
assertEquals(1, resultList.size());
assertTrue(
resultList.stream()
.anyMatch(e -> e.getIdUser().equals(linkedEntrepreneur.getIdUser())));
assertFalse(
resultList.stream()
.anyMatch(e -> e.getIdUser().equals(otherEntrepreneur.getIdUser())));
}
/*
* Tests retrieving appointments linked to a project's section cells when the user is authorized
* and such appointments exist.
* Verifies that the correct appointments are returned.
*/
// Tests getAppointmentsByProjectId
/*@Test*/
// Commenting out failing test
void testGetAppointmentsByProjectId_Authorized_Found() {
// Arrange: Create specific SectionCells and Appointments for this test
SectionCell cell1 =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 1L, "Cell 1 Test", LocalDateTime.now()));
SectionCell cell2 =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 2L, "Cell 2 Test", LocalDateTime.now()));
Project otherProject =
projectService.addNewProject(
getTestProject(
"other_project_app_test",
administratorService.addAdministrator(
getTestAdmin("other_admin_app_test"))));
SectionCell otherProjectCell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
otherProject,
1L,
"Other Project Cell App Test",
LocalDateTime.now()));
Appointment app1 =
getTestAppointment(
LocalDate.now().plusDays(10),
LocalTime.NOON,
LocalTime.of(0, 30),
"Place 1 App Test",
"Subject 1 App Test",
List.of(cell1),
null);
Appointment savedApp1 = appointmentService.addNewAppointment(app1);
Appointment app2 =
getTestAppointment(
LocalDate.now().plusDays(11),
LocalTime.NOON.plusHours(1),
LocalTime.of(1, 0),
"Place 2 App Test",
"Subject 2 App Test",
List.of(cell1, cell2),
null);
Appointment savedApp2 = appointmentService.addNewAppointment(app2);
Appointment otherApp =
getTestAppointment(
LocalDate.now().plusDays(12),
LocalTime.MIDNIGHT,
LocalTime.of(0, 15),
"Other Place App Test",
"Other Subject App Test",
List.of(otherProjectCell),
null);
appointmentService.addNewAppointment(otherApp);
// Act
Iterable<Appointment> result =
sharedApiService.getAppointmentsByProjectId(
staticAuthorizedProject.getIdProject(), // Use static project ID
staticAuthorizedMail); // Use static authorized mail
List<Appointment> resultList = TestUtils.toList(result);
// Assert
assertEquals(2, resultList.size());
assertTrue(
resultList.stream()
.anyMatch(a -> a.getIdAppointment().equals(savedApp1.getIdAppointment())));
assertTrue(
resultList.stream()
.anyMatch(a -> a.getIdAppointment().equals(savedApp2.getIdAppointment())));
assertFalse(
resultList.stream()
.anyMatch(
a ->
a.getIdAppointment()
.equals(otherApp.getIdAppointment()))); // Ensure
// appointment from other project is not included
}
/*
* Tests generating a PDF report for an appointment when the user is authorized
* for the project linked to the appointment's section cell.
* Verifies that no authorization exception is thrown. (Note: File I/O is mocked).
*/
// Tests getPDFReport (Focus on authorization and data retrieval flow)
/*@Test*/
// Commenting out failing test
void testGetPDFReport_Authorized() throws DocumentException, URISyntaxException, IOException {
// Arrange: Create a specific appointment linked to the static authorized project
SectionCell cell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
1L,
"Cell for PDF Test",
LocalDateTime.now()));
Report report =
new Report(null, "PDF Report Content // Point 2 PDF Content"); // ID set by DB
Appointment appointment =
getTestAppointment(
LocalDate.now().plusDays(20),
LocalTime.of(14, 0),
LocalTime.of(0, 45),
"Salle PDF",
"PDF Subject",
List.of(cell),
report);
Appointment savedAppointment = appointmentService.addNewAppointment(appointment);
// Mock getAppointmentById to return the saved appointment for the service to use
when(appointmentService.getAppointmentById(eq(savedAppointment.getIdAppointment())))
.thenReturn(savedAppointment);
// mockUtilsService is configured in BeforeEach to allow staticAuthorizedMail for
// staticAuthorizedProject
// Act & Assert (Just assert no authorization exception is thrown)
assertDoesNotThrow(
() ->
sharedApiService.getPDFReport(
savedAppointment.getIdAppointment(), staticAuthorizedMail));
// Note: Actual PDF generation and file operations are not tested here,
// as that requires mocking external libraries and file system operations.
}
/*
* Tests generating a PDF report for an appointment when the user is not authorized
* for the project linked to the appointment's section cell.
* Verifies that an Unauthorized ResponseStatusException is thrown.
*/
/*@Test*/
// Commenting out failing test
void testGetPDFReport_Unauthorized() {
// Arrange: Create a specific appointment linked to the static *unauthorized* project
SectionCell cell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticUnauthorizedProject,
1L,
"Cell for Unauthorized PDF Test",
LocalDateTime.now()));
Report report = new Report(null, "Unauthorized PDF Report Content");
Appointment appointment =
getTestAppointment(
LocalDate.now().plusDays(21),
LocalTime.of(15, 0),
LocalTime.of(0, 30),
"Salle Unauthorized PDF",
"Unauthorized PDF Subject",
List.of(cell),
report);
Appointment savedAppointment = appointmentService.addNewAppointment(appointment);
// Mock getAppointmentById to return the saved appointment
when(appointmentService.getAppointmentById(eq(savedAppointment.getIdAppointment())))
.thenReturn(savedAppointment);
// mockUtilsService is configured in BeforeEach to DENY staticUnauthorizedMail for
// staticUnauthorizedProject
// Act & Assert
ResponseStatusException exception =
assertThrows(
ResponseStatusException.class,
() -> {
sharedApiService.getPDFReport(
savedAppointment.getIdAppointment(),
staticUnauthorizedMail); // Unauthorized user mail
});
assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode());
}
}

View File

@ -16,7 +16,7 @@ BACKEND_PASSWORD=backend_db_user_password
DATABASE_URL=localhost:5433
VITE_KEYCLOAK_URL=http://localhost:7080
VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev
VITE_KEYCLOAK_REALM=test
VITE_KEYCLOAK_CLIENT_ID=MyINPulse-vite
VITE_KEYCLOAK_REALM=MyINPulse
VITE_APP_URL=http://localhost:5173
VITE_BACKEND_URL=http://localhost:8081/

View File

@ -22,6 +22,8 @@ paths:
description: Bad Request - Invalid project data provided (e.g., missing required fields).
"401":
description: Unauthorized - Authentication required or invalid token.
"403":
description: Bad Token - Invalid Keycloack configuration.
post:
operationId: addProjectManually
@ -39,7 +41,7 @@ paths:
schema:
$ref: "./main.yaml#/components/schemas/project"
responses:
"201": # Use 201 Created for successful creation
"200": # Use 200 Created for successful creation
description: Created - Project added successfully. Returns the created project.
content:
application/json:
@ -49,6 +51,8 @@ paths:
description: Bad Request - Project already exists.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/projects/pending:
@ -71,6 +75,8 @@ paths:
$ref: "./main.yaml#/components/schemas/project" # Assuming pending projects use the same schema
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/request-join:
get:
@ -92,6 +98,8 @@ paths:
$ref: "./main.yaml#/components/schemas/joinRequest"
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/request-join/decision/{joinRequestId}:
post:
@ -122,6 +130,8 @@ paths:
description: Bad Request - Invalid input (e.g., missing decision).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/projects/pending/decision:
@ -136,14 +146,6 @@ paths:
If rejected (isAccepted=false), the pending project data might be archived or deleted based on business logic.
security:
- MyINPulse: [MyINPulse-admin]
parameters:
- in: path
name: pendingProjectId # Corrected typo and name change
required: true
schema:
type: integer
description: The ID of the pending project to decide upon.
example: 7
requestBody:
required: true
description: Decision payload.
@ -152,12 +154,14 @@ paths:
schema:
$ref: './main.yaml#/components/schemas/projectDecision'
responses:
"204": # Use 204 No Content for successful action with no body
"200": # Use 200 No Content for successful action with no body
description: No Content - Decision processed successfully.
"400":
description: Bad Request - Invalid input (e.g., missing decision).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/pending-accounts: # Path updated
@ -180,6 +184,8 @@ paths:
$ref: "./main.yaml#/components/schemas/user-entrepreneur"
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/accounts/validate/{userId}:
post: # Changed to POST as it changes state
@ -199,11 +205,12 @@ paths:
description: The ID of the user account to validate.
example: 102
responses:
"204":
"200":
description: No Content - Account validated successfully.
"400":
description: Bad Request - Invalid user ID format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -225,6 +232,8 @@ paths:
type: array
items:
$ref: "./main.yaml#/components/schemas/appointment"
"403":
description: Bad Token - Invalid Keycloack configuration.
"404":
description: no appointments found.
"401":
@ -255,13 +264,15 @@ paths:
schema:
$ref: "./main.yaml#/components/schemas/report"
responses:
"201":
"200":
description: Created - Report created and linked successfully. Returns the created report.
content:
application/json:
schema: { $ref: "./main.yaml#/components/schemas/report" }
"400":
description: Bad Request - Invalid input (e.g., missing content, invalid appointment ID format).
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -296,6 +307,8 @@ paths:
schema: { $ref: "./main.yaml#/components/schemas/report" }
"400":
description: Bad Request - Invalid input (e.g., missing content).
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -318,10 +331,12 @@ paths:
description: The ID of the project to remove.
example: 12
responses:
"204":
"200":
description: No Content - Project removed successfully.
"400":
description: Bad Request - Invalid project ID format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -345,9 +360,28 @@ paths:
description: The ID of the user to grant admin rights.
example: 103
responses:
"204": # Use 204 No Content
"200": # Use 200 No Content
description: No Content - Admin rights granted successfully.
"400":
description: Bad Request - Invalid user ID format or user is already an admin.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
/admin/create-account:
post:
summary: Creates Admin out Jwt Token
tags:
- Admin API
security:
- MyINPulse: [MyINPulse-admin]
description: Create an admin instance in the MyINPulse DB of the information provided from the authenticated user's keycloack token.
The information required in the token are `userSurname`, `username`, `primaryMail`, `secondaryMail`, `phoneNumber`.
responses:
"200":
description: No Content - Admin user created successfully.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -21,12 +21,14 @@ paths:
schema:
$ref: "./main.yaml#/components/schemas/project"
responses:
"202":
"200":
description: Accepted - Project creation request received and is pending validation.
"400":
description: Bad Request - Invalid input (e.g., missing name).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/sectionCells: # Base path
post:
@ -46,12 +48,14 @@ paths:
schema:
$ref: "./main.yaml#/components/schemas/sectionCell"
responses:
"201":
"200":
description: Created - Section cell added successfully. Returns the created cell.
"400":
description: Bad Request - Invalid input (e.g., missing content or sectionId).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/sectionCells/{sectionCellId}:
put:
@ -84,6 +88,8 @@ paths:
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
delete:
operationId: removeSectionCell
@ -102,7 +108,7 @@ paths:
description: The ID of the section cell to remove.
example: 509
responses:
"204":
"200":
description: No Content - Section cell removed successfully.
"400":
description: Bad Request - Invalid ID format.
@ -110,3 +116,82 @@ paths:
description: Bad Request - sectionCell not found.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects:
get:
summary: gets the projectId of the project associated with the entrepreneur
description: returns a list of projectIds of the projects associated with the entrepreneur
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - Section cell updated successfully. Returns the updated cell.
content:
application/json:
schema:
type: array
items:
$ref: "./main.yaml#/components/schemas/project"
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects/project-is-active:
get:
summary: checks if the project associated with an entrepreneur is active
description: returns a boolean if the project associated with an entrepreneur has an active status
(i.e has been validated by an admin). The user should be routed to LeanCanvas. any other response code
should be treated as false
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - got the value successfully any other response code should be treated as false.
content:
application/json:
schema:
type: boolean
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects/has-pending-request:
get:
summary: checks if the user has a pending projectRequest
description: returns a boolean if the project associated with an entrepreneur has a pending status
(i.e has not yet been validated by an admin). The user should be routed to a page telling him that he should
wait for admin validation. any other response code should be treated as false.
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - got the value successfully any other response code should be treated as false.
content:
application/json:
schema:
type: boolean
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -79,6 +79,10 @@ paths:
$ref: "./unauthApi.yaml#/paths/~1unauth~1finalize"
/unauth/request-join/{projectId}:
$ref: "./unauthApi.yaml#/paths/~1unauth~1request-join~1{projectId}"
/unauth/request-admin-role:
$ref: "./unauthApi.yaml#/paths/~1unauth~1request-admin-role"
/unauth/check-if-not-pending:
$ref: "./unauthApi.yaml#/paths/~1unauth~1check-if-not-pending"
# _ ____ __ __ ___ _ _ _ ____ ___
# / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _|
@ -108,6 +112,8 @@ paths:
$ref: "./adminApi.yaml#/paths/~1admin~1projects~1{projectId}"
/admin/make-admin/{userId}:
$ref: "./adminApi.yaml#/paths/~1admin~1make-admin~1{userId}"
/admin/create-account:
$ref: "./adminApi.yaml#/paths/~1admin~1create-account"
# ____ _ _ _ ____ ___
# / ___|| |__ __ _ _ __ ___ __| | / \ | _ \_ _|
@ -138,9 +144,16 @@ paths:
# / ___ \| __/| |
# /_/ \_\_| |___|
#
/entrepreneur/projects:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects"
/entrepreneur/projects/request:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1request"
/entrepreneur/sectionCells:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells"
/entrepreneur/sectionCells/{sectionCellId}:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells~1{sectionCellId}"
/entrepreneur/projects/project-is-active:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1project-is-active"
/entrepreneur/projects/has-pending-request:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1has-pending-request"

View File

@ -37,6 +37,8 @@ paths:
$ref: "./main.yaml#/components/schemas/sectionCell"
"400":
description: Bad Request - Invalid parameter format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -68,7 +70,7 @@ paths:
"401":
description: Unauthorized.
"403":
description: Forbidden - User does not have access to this project.
description: Bad Token - Invalid Keycloack configuration.
"404":
description: Not Found - Project not found.
@ -97,7 +99,7 @@ paths:
"401":
description: Unauthorized.
"403":
description: Forbidden - User does not have access to this project.
description: Bad Token - Invalid Keycloack configuration.
"404":
description: Not Found - Project not found.
@ -126,6 +128,8 @@ paths:
type: array
items:
$ref: "./main.yaml#/components/schemas/appointment"
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -156,6 +160,8 @@ paths:
format: binary
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/shared/appointments/request:
@ -176,11 +182,12 @@ paths:
$ref: "./main.yaml#/components/schemas/appointment" # Assuming request uses same model structure
# Potentially add projectId or targetUserId here
responses:
"202": # Accepted seems appropriate for a request
"200": # Accepted seems appropriate for a request
description: Accepted - Appointment request submitted.
"400":
description: Bad Request - Invalid appointment details.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -18,12 +18,14 @@ paths:
tags:
- Unauth API
responses:
"201":
"200":
description: Created - Account finalized and pending admin validation. Returns the user profile.
"400":
description: Bad Request - Problem processing the token or user data derived from it.
"401":
description: Unauthorized - Valid authentication token required.
"403":
description: Bad Token - Invalid Keycloack configuration.
/unauth/request-join/{projectId}:
post:
summary: Request to join an existing project
@ -39,7 +41,7 @@ paths:
description: The ID of the project to request joining.
example: 15
responses: # Moved responses block to correct level
"202":
"200":
description: Accepted - Join request submitted and pending approval.
"400":
description: Bad Request - Invalid project ID format
@ -47,16 +49,42 @@ paths:
description: Already member/request pending.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/unauth/request-admin-role:
post:
summary: Request to join an existing project
summary: Request to become an admin
description: Submits a request for the authenticated user (keycloack authenticated) to become an admin. Their role is then changed to admin in server and Keycloak. This requires approval from a project admin.
tags:
- Unauth API
responses:
"202":
"200":
description: Accepted - Become admin request submitted and pending approval.
"400":
description: Bad Request - Invalid project ID format or already member/request pending.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/unauth/check-if-not-pending:
get:
summary: Returns a boolean of whether the user's account is not pending
description: Returns a boolean with value `true` if the user's account is not pending and `false` if it is.
tags:
- Unauth API
responses:
"200":
description: Accepted - Become admin request submitted and pending approval.
content:
application/json:
schema:
type: boolean
"400":
description: Bad Request - Invalid project ID format or already member/request pending.
"401":
description: Unauthorized.
"404":
description: Bad Request - User not found in database.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -58,7 +58,7 @@ const USERID = ref("");
<tr>
<td>Get Pending Accounts</td>
<td>
<button @click="callApi('admin/get_pending_accounts')">
<button @click="callApi('/admin/pending-accounts')">
call
</button>
</td>

2638
keycloak/realm.json Normal file

File diff suppressed because it is too large Load Diff