Merge branch 'develop' into delete-forum-replies

This commit is contained in:
Andreas Svanberg 2025-03-17 13:41:29 +01:00
commit 7d82bb50b2
11 changed files with 182 additions and 9 deletions
core
pom.xml
src
main/java/se/su/dsv/scipro/match
test/java/se/su/dsv/scipro
pom.xml
test-data/src/main/java/se/su/dsv/scipro/testdata
view/src/main/java/se/su/dsv/scipro

@ -32,6 +32,10 @@
<groupId>org.glassfish.jersey.media</groupId> <groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId> <artifactId>jersey-media-json-jackson</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>

@ -235,10 +235,8 @@ public class IdeaServiceImpl extends AbstractServiceImpl<Idea, Long> implements
if (authorParticipatingOnActiveIdea(coAuthor, ap)) { if (authorParticipatingOnActiveIdea(coAuthor, ap)) {
return new Pair<>(Boolean.FALSE, PARTNER_ALREADY_PARTICIPATING_ERROR); return new Pair<>(Boolean.FALSE, PARTNER_ALREADY_PARTICIPATING_ERROR);
} }
if ( List<ProjectType> typesForCoAuthor = applicationPeriodService.getTypesForStudent(ap, coAuthor);
coAuthor.getDegreeType() != ProjectType.UNKNOWN && if (!typesForCoAuthor.contains(idea.getProjectType())) {
coAuthor.getDegreeType() != idea.getProjectType().getDegreeType()
) {
return new Pair<>(Boolean.FALSE, WRONG_LEVEL_FOR_YOUR_PARTNER); return new Pair<>(Boolean.FALSE, WRONG_LEVEL_FOR_YOUR_PARTNER);
} }
if (!projectService.getActiveProjectsByUserAndProjectType(coAuthor, idea.getProjectType()).isEmpty()) { if (!projectService.getActiveProjectsByUserAndProjectType(coAuthor, idea.getProjectType()).isEmpty()) {

@ -241,6 +241,7 @@ public class IdeaServiceImplTest {
when(generalSystemSettingsService.getGeneralSystemSettingsInstance()).thenReturn(new GeneralSystemSettings()); when(generalSystemSettingsService.getGeneralSystemSettingsInstance()).thenReturn(new GeneralSystemSettings());
Idea idea = createBachelorIdea(Idea.Status.UNMATCHED); Idea idea = createBachelorIdea(Idea.Status.UNMATCHED);
when(applicationPeriodService.getTypesForStudent(applicationPeriod, student)).thenReturn(List.of(bachelor)); when(applicationPeriodService.getTypesForStudent(applicationPeriod, student)).thenReturn(List.of(bachelor));
when(applicationPeriodService.getTypesForStudent(applicationPeriod, coAuthor)).thenReturn(List.of(bachelor));
Pair<Boolean, String> acceptance = ideaService.validateStudentAcceptance( Pair<Boolean, String> acceptance = ideaService.validateStudentAcceptance(
idea, idea,
@ -401,6 +402,39 @@ public class IdeaServiceImplTest {
assertEquals(expected, ideaService.countAuthorsByApplicationPeriod(applicationPeriod, params)); assertEquals(expected, ideaService.countAuthorsByApplicationPeriod(applicationPeriod, params));
} }
@Test
public void wrong_type_for_author() {
when(applicationPeriodService.getTypesForStudent(applicationPeriod, student)).thenReturn(List.of(master));
when(applicationPeriodService.getTypesForStudent(applicationPeriod, coAuthor)).thenReturn(List.of(bachelor));
assertPair(
false,
"The idea is the wrong level for you, please pick another one.",
ideaService.validateStudentAcceptance(
createBachelorIdea(Idea.Status.UNMATCHED),
student,
coAuthor,
applicationPeriod
)
);
}
@Test
public void wrong_type_for_partner() {
when(applicationPeriodService.getTypesForStudent(applicationPeriod, coAuthor)).thenReturn(List.of(master));
assertPair(
false,
"The idea is the wrong level for your partner, please pick another one.",
ideaService.validateStudentAcceptance(
createBachelorIdea(Idea.Status.UNMATCHED),
student,
coAuthor,
applicationPeriod
)
);
}
private Idea mockInactiveIdea() { private Idea mockInactiveIdea() {
Idea idea = new Idea(); Idea idea = new Idea();
Match match = new Match(); Match match = new Match();

@ -6,7 +6,11 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.sun.net.httpserver.HttpServer;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.Month; import java.time.Month;
import java.util.*; import java.util.*;
@ -15,6 +19,9 @@ import org.junit.jupiter.api.Test;
import se.su.dsv.scipro.finalseminar.FinalSeminar; import se.su.dsv.scipro.finalseminar.FinalSeminar;
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
import se.su.dsv.scipro.finalseminar.OppositionApprovedEvent; import se.su.dsv.scipro.finalseminar.OppositionApprovedEvent;
import se.su.dsv.scipro.grading.GetGradeError;
import se.su.dsv.scipro.grading.GradingServiceImpl;
import se.su.dsv.scipro.grading.Result;
import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.project.Project;
import se.su.dsv.scipro.security.auth.roles.Roles; import se.su.dsv.scipro.security.auth.roles.Roles;
import se.su.dsv.scipro.system.DegreeType; import se.su.dsv.scipro.system.DegreeType;
@ -149,6 +156,44 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
assertNull(oppositionCriterion.getFeedback()); assertNull(oppositionCriterion.getFeedback());
} }
@Test
public void test_json_deserialization() throws IOException {
String json =
"""
{
"grade": "A",
"reported": "2021-01-01"
}
""";
HttpServer httpServer = startHttpServerWithJsonResponse(json);
int port = httpServer.getAddress().getPort();
GradingServiceImpl gradingService = new GradingServiceImpl("http://localhost:" + port);
Either<GetGradeError, Optional<Result>> result = gradingService.getResult("token", 1, 2, 3);
Optional<Result> right = result.right();
assertTrue(right.isPresent());
assertEquals(LocalDate.of(2021, 1, 1), right.get().reported());
httpServer.stop(0);
}
private static HttpServer startHttpServerWithJsonResponse(String json) throws IOException {
HttpServer httpServer = HttpServer.create();
httpServer.createContext("/", exchange -> {
try (exchange) {
byte[] response = json.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(200, response.length);
exchange.getResponseBody().write(response);
}
});
httpServer.bind(new InetSocketAddress("localhost", 0), 0);
httpServer.start();
return httpServer;
}
private void addOppositionCriterion() { private void addOppositionCriterion() {
gradingReportTemplate = createOppositionCriteria(gradingReportTemplate, 2); gradingReportTemplate = createOppositionCriteria(gradingReportTemplate, 2);
gradingReport = createGradingReport(project, student); gradingReport = createGradingReport(project, student);

@ -26,7 +26,7 @@
<wicket.version>10.4.0</wicket.version> <wicket.version>10.4.0</wicket.version>
<jakarta.persistence-api.version>3.1.0</jakarta.persistence-api.version> <jakarta.persistence-api.version>3.1.0</jakarta.persistence-api.version>
<querydsl.version>5.0.0</querydsl.version> <querydsl.version>5.0.0</querydsl.version>
<poi.version>5.2.5</poi.version> <poi.version>5.4.0</poi.version>
<!-- <!--
When updating spring-boot check if the transitive dependency on json-smart has been When updating spring-boot check if the transitive dependency on json-smart has been

@ -1,6 +1,9 @@
package se.su.dsv.scipro.testdata; package se.su.dsv.scipro.testdata;
import java.util.List;
import se.su.dsv.scipro.match.Keyword;
import se.su.dsv.scipro.system.ProjectType; import se.su.dsv.scipro.system.ProjectType;
import se.su.dsv.scipro.system.ResearchArea;
/// All the base test data that can be re-used in different test cases. /// All the base test data that can be re-used in different test cases.
/// ///
@ -16,4 +19,11 @@ public interface BaseData {
ProjectType bachelor(); ProjectType bachelor();
ProjectType magister(); ProjectType magister();
ProjectType master(); ProjectType master();
/**
* @return generic research area with some keywords attached to it
*/
ResearchAreaAndKeywords researchArea();
record ResearchAreaAndKeywords(ResearchArea researchArea, List<Keyword> keywords) {}
} }

@ -2193,6 +2193,11 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
return createEmployee(firstName); return createEmployee(firstName);
} }
@Override
public ResearchAreaAndKeywords researchArea() {
return new ResearchAreaAndKeywords(researchArea1, List.of(keyword1, keyword2));
}
private static final class SimpleTextFile implements FileUpload { private static final class SimpleTextFile implements FileUpload {
private final User uploader; private final User uploader;

@ -0,0 +1,68 @@
package se.su.dsv.scipro.testdata.populators;
import jakarta.inject.Inject;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Set;
import org.springframework.stereotype.Service;
import se.su.dsv.scipro.match.ApplicationPeriod;
import se.su.dsv.scipro.match.ApplicationPeriodService;
import se.su.dsv.scipro.match.Idea;
import se.su.dsv.scipro.match.IdeaService;
import se.su.dsv.scipro.match.Target;
import se.su.dsv.scipro.match.TargetService;
import se.su.dsv.scipro.system.User;
import se.su.dsv.scipro.testdata.BaseData;
import se.su.dsv.scipro.testdata.Factory;
import se.su.dsv.scipro.testdata.TestDataPopulator;
@Service
public class PartnerTypeExemption implements TestDataPopulator {
private final ApplicationPeriodService applicationPeriodService;
private final IdeaService ideaService;
private final TargetService targetService;
@Inject
public PartnerTypeExemption(
ApplicationPeriodService applicationPeriodService,
IdeaService ideaService,
TargetService targetService
) {
this.applicationPeriodService = applicationPeriodService;
this.ideaService = ideaService;
this.targetService = targetService;
}
@Override
public void populate(BaseData baseData, Factory factory) {
factory.createAuthor("Oskar");
User johan = factory.createAuthor("Johan");
johan.setDegreeType(baseData.master().getDegreeType());
User supervisor = factory.createSupervisor("Elsa");
ApplicationPeriod applicationPeriod = new ApplicationPeriod("Supervisor ideas");
applicationPeriod.setStartDate(LocalDate.now());
applicationPeriod.setEndDate(LocalDate.now().plusDays(14));
applicationPeriod.setCourseStartDateTime(LocalDateTime.now().plusDays(15));
applicationPeriod.setProjectTypes(Set.of(baseData.bachelor()));
applicationPeriodService.save(applicationPeriod);
Target target = targetService.findOne(applicationPeriod, supervisor, baseData.bachelor());
target.setTarget(10);
targetService.save(target);
Idea idea = new Idea();
idea.setPublished(true);
idea.setTitle("The next gen AI 2.0 turbo edition");
idea.setPrerequisites("Hacker experience");
idea.setDescription("Better than all the rest");
idea.setProjectType(baseData.bachelor());
idea.setApplicationPeriod(applicationPeriod);
idea.setResearchArea(baseData.researchArea().researchArea());
ideaService.saveSupervisorIdea(idea, supervisor, baseData.researchArea().keywords(), true);
}
}

@ -1,6 +1,5 @@
package se.su.dsv.scipro.datatables.project; package se.su.dsv.scipro.datatables.project;
import com.google.common.eventbus.EventBus;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import java.util.*; import java.util.*;
import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.AjaxRequestTarget;
@ -33,7 +32,6 @@ import se.su.dsv.scipro.components.datatables.MultipleUsersColumn;
import se.su.dsv.scipro.components.datatables.UserColumn; import se.su.dsv.scipro.components.datatables.UserColumn;
import se.su.dsv.scipro.dataproviders.FilteredDataProvider; import se.su.dsv.scipro.dataproviders.FilteredDataProvider;
import se.su.dsv.scipro.datatables.AjaxCheckboxWrapper; import se.su.dsv.scipro.datatables.AjaxCheckboxWrapper;
import se.su.dsv.scipro.notifications.NotificationController;
import se.su.dsv.scipro.profile.UserLinkPanel; import se.su.dsv.scipro.profile.UserLinkPanel;
import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.project.Project;
import se.su.dsv.scipro.project.ProjectService; import se.su.dsv.scipro.project.ProjectService;
@ -45,7 +43,6 @@ import se.su.dsv.scipro.system.ProjectType;
import se.su.dsv.scipro.system.ProjectTypeService; import se.su.dsv.scipro.system.ProjectTypeService;
import se.su.dsv.scipro.system.ResearchArea; import se.su.dsv.scipro.system.ResearchArea;
import se.su.dsv.scipro.system.User; import se.su.dsv.scipro.system.User;
import se.su.dsv.scipro.system.UserService;
import se.su.dsv.scipro.util.PageParameterKeys; import se.su.dsv.scipro.util.PageParameterKeys;
public class ProjectDataPanel extends Panel { public class ProjectDataPanel extends Panel {
@ -170,6 +167,11 @@ public class ProjectDataPanel extends Panel {
) { ) {
cellItem.add(new ReviewerColumnCell(componentId, rowModel)); cellItem.add(new ReviewerColumnCell(componentId, rowModel));
} }
@Override
public IModel<?> getDataModel(IModel<Project> rowModel) {
return rowModel.map(Project::getReviewer).map(User::getFullName);
}
}; };
} }

@ -59,6 +59,11 @@ public class EditGroupPanel extends Panel {
}); });
add( add(
new ListView<>("available_projects", availableProjects) { new ListView<>("available_projects", availableProjects) {
{
// must re-use list items to maintain form component (checkboxes) state
setReuseItems(true);
}
@Override @Override
protected void populateItem(ListItem<Project> item) { protected void populateItem(ListItem<Project> item) {
CheckBox checkbox = new CheckBox("selected", new SelectProjectModel(model, item.getModel())); CheckBox checkbox = new CheckBox("selected", new SelectProjectModel(model, item.getModel()));

@ -72,7 +72,9 @@ public class ExcelExporter extends AbstractDataExporter {
for (int i = 0; i < columns.size(); i++) { for (int i = 0; i < columns.size(); i++) {
Object cellValue = columns.get(i).getDataModel(data).getObject(); Object cellValue = columns.get(i).getDataModel(data).getObject();
Cell cell = row.createCell(i); Cell cell = row.createCell(i);
cell.setCellValue(String.valueOf(cellValue)); if (cellValue != null) {
cell.setCellValue(cellValue.toString());
}
} }
} }
} }