diff --git a/.gitignore b/.gitignore index a4397c6105..8393ae6082 100755 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ fitnesse/target/ daisy-integration/target/ war/target/ api/target/ +test-data/target/ node_modules/ diff --git a/Dockerfile b/Dockerfile index ac53fd07f3..acf3c889a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,12 +12,14 @@ COPY core/pom.xml core/pom.xml COPY view/pom.xml view/pom.xml COPY war/pom.xml war/pom.xml COPY daisy-integration/pom.xml daisy-integration/pom.xml +COPY test-data/pom.xml test-data/pom.xml COPY api/src/ api/src/ COPY core/src/ core/src/ COPY view/src/ view/src/ COPY war/src/ war/src/ COPY daisy-integration/src/ daisy-integration/src/ +COPY test-data/src/ test-data/src/ RUN ./mvnw package \ --define skipTests \ diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java index b7c51b41f2..19b8f04a13 100644 --- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java +++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java @@ -1017,11 +1017,6 @@ public class CoreConfig { ); } - @Bean - public DataInitializer dataInitializer() { - return new DataInitializer(); - } - @Bean public FinalSeminarActivityHandler finalSeminarActivityHandler( ActivityPlanFacade activityPlanFacade, diff --git a/pom.xml b/pom.xml index 1940f2ed25..896b1269e5 100755 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ <module>daisy-integration</module> <module>war</module> <module>api</module> + <module>test-data</module> </modules> <properties> diff --git a/test-data/pom.xml b/test-data/pom.xml new file mode 100644 index 0000000000..127cef4d71 --- /dev/null +++ b/test-data/pom.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>se.su.dsv.scipro</groupId> + <artifactId>SciPro</artifactId> + <version>0.1-SNAPSHOT</version> + </parent> + + <artifactId>test-data</artifactId> + + <dependencies> + <dependency> + <groupId>se.su.dsv.scipro</groupId> + <artifactId>core</artifactId> + </dependency> + </dependencies> +</project> diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/BaseData.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/BaseData.java new file mode 100644 index 0000000000..1dd6b18132 --- /dev/null +++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/BaseData.java @@ -0,0 +1,19 @@ +package se.su.dsv.scipro.testdata; + +import se.su.dsv.scipro.system.ProjectType; + +/// All the base test data that can be re-used in different test cases. +/// +/// **Do not modify any of this data.** There are many +/// [TestDataPopulator]s that rely on this data to be in a specific state. +/// +/// In addition to the data that is available here there is also much additional +/// data that has been created; +/// +/// - A grading report template for each [ProjectType] +/// +public interface BaseData { + ProjectType bachelor(); + ProjectType magister(); + ProjectType master(); +} diff --git a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java similarity index 99% rename from core/src/main/java/se/su/dsv/scipro/DataInitializer.java rename to test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java index 15a093006b..8b157598c4 100644 --- a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java +++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java @@ -1,4 +1,4 @@ -package se.su.dsv.scipro; +package se.su.dsv.scipro.testdata; import jakarta.inject.Inject; import jakarta.inject.Provider; @@ -34,13 +34,16 @@ import se.su.dsv.scipro.security.auth.roles.Roles; import se.su.dsv.scipro.system.*; import se.su.dsv.scipro.util.Pair; -public class DataInitializer implements Lifecycle { +public class DataInitializer implements Lifecycle, BaseData, Factory { public static final int APPLICATION_PERIOD_START_MINUS_DAYS = 1; public static final int APPLICATION_PERIOD_END_PLUS_DAYS = 3; public static final int APPLICATION_PERIOD_COURSE_START_PLUS_DAYS = 5; public static final long RESEARCH_AREA_ID = 12L; + @Inject + private Collection<TestDataPopulator> testDataPopulators = new ArrayList<>(); + @Inject private UserService userService; @@ -120,6 +123,9 @@ public class DataInitializer implements Lifecycle { createTarget(); createStudentIdea(); createRoughDraftApproval(); + for (TestDataPopulator testDataPopulator : testDataPopulators) { + testDataPopulator.populate(this, this); + } } if (profile.getCurrentProfile() == Profiles.DEV && noAdminUser()) { createAdmin(); @@ -243,13 +249,18 @@ public class DataInitializer implements Lifecycle { sofia_student = createStudent("Sofia", 17); } - private User createStudent(String firstName, int identifier) { + private User createStudent(String firstName) { User user = createUser(firstName, STUDENT_LAST); - user.setIdentifier(identifier); createBeta(user); return user; } + private User createStudent(String firstName, int identifier) { + User user = createStudent(firstName); + user.setIdentifier(identifier); + return user; + } + private User createEmployee(String firstName) { User user = createUser(firstName, EMPLOYEE_LAST); Unit u = createUnit(); @@ -2087,6 +2098,36 @@ public class DataInitializer implements Lifecycle { return entity; } + @Override + public ProjectType bachelor() { + return bachelorClass; + } + + @Override + public ProjectType magister() { + return magisterClass; + } + + @Override + public ProjectType master() { + return masterClass; + } + + @Override + public User createAuthor(String firstName) { + return createStudent(firstName); + } + + @Override + public User createSupervisor(String firstName) { + return createEmployee(firstName); + } + + @Override + public User createReviewer(String firstName) { + return createEmployee(firstName); + } + private static final class SimpleTextFile implements FileUpload { private final User uploader; diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/Factory.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/Factory.java new file mode 100644 index 0000000000..fc4c0910f1 --- /dev/null +++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/Factory.java @@ -0,0 +1,46 @@ +package se.su.dsv.scipro.testdata; + +import se.su.dsv.scipro.security.auth.roles.Roles; +import se.su.dsv.scipro.system.User; + +/** + * A factory to help with repetitive tasks when populating test data. + */ +public interface Factory { + /** + * Creates a user with the given first name and last name "Student". + * The user is given the role {@link Roles#AUTHOR}. + * <p> + * A username is created of the form {@code <first_name>@example.com} that + * can be used to log in. + */ + User createAuthor(String firstName); + + /** + * Creates a user with the given first name and last name "Employee". + * <p> + * The user is given the role {@link Roles#SUPERVISOR}, {@link Roles#REVIEWER}, + * and {@link Roles#EXAMINER}. + * <p> + * The user gets a default research area, unit, and language. It is also + * marked as {@link User#setActiveAsSupervisor(boolean) an active supervisor}. + * <p> + * A username is created of the form {@code <first_name>@example.com} that + * can be used to log in. + */ + User createSupervisor(String firstName); + + /** + * Creates a user with the given first name and last name "Employee". + * <p> + * The user is given the role {@link Roles#SUPERVISOR}, {@link Roles#REVIEWER}, + * and {@link Roles#EXAMINER}. + * <p> + * The user gets a default research area, unit, and language. It is also + * marked as {@link User#setActiveAsSupervisor(boolean) an active supervisor}. + * <p> + * A username is created of the form {@code <first_name>@example.com} that + * can be used to log in. + */ + User createReviewer(String firstName); +} diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataConfiguration.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataConfiguration.java new file mode 100644 index 0000000000..ffd372628b --- /dev/null +++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataConfiguration.java @@ -0,0 +1,16 @@ +package se.su.dsv.scipro.testdata; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import se.su.dsv.scipro.war.PluginConfiguration; + +@Configuration(proxyBeanMethods = false) +@ComponentScan(basePackages = "se.su.dsv.scipro.testdata.populators") +public class TestDataConfiguration implements PluginConfiguration { + + @Bean + public DataInitializer dataInitializer() { + return new DataInitializer(); + } +} diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataPopulator.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataPopulator.java new file mode 100644 index 0000000000..41d250ac71 --- /dev/null +++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataPopulator.java @@ -0,0 +1,11 @@ +package se.su.dsv.scipro.testdata; + +public interface TestDataPopulator { + /** + * Add test data to the system to help with testing a specific feature. + * + * @param baseData the base data already populated + * @param factory helper object to make repetitive tasks easier (such as creating users) + */ + void populate(BaseData baseData, Factory factory); +} diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/package-info.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/package-info.java new file mode 100644 index 0000000000..6b93353f29 --- /dev/null +++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/package-info.java @@ -0,0 +1,8 @@ +/** + * This package contains the infrastructure that is used when generating test data for the application. To add new test + * data to the system, add a new class to the {@link se.su.dsv.scipro.testdata.populators} package that implements the + * {@link se.su.dsv.scipro.testdata.TestDataPopulator} interface and annotate it with + * {@link org.springframework.stereotype.Service @Service}. Inject dependencies as needed using + * {@link jakarta.inject.Inject @Inject}. + */ +package se.su.dsv.scipro.testdata; diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/package-info.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/package-info.java new file mode 100644 index 0000000000..c9d116ca5a --- /dev/null +++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/package-info.java @@ -0,0 +1,13 @@ +/** + * Contains classes that populate the database with test data. + * <p> + * Prefer to use methods on the various services to create data, rather than directly interacting with the database + * using an {@link jakarta.persistence.EntityManager}. This is to make sure all business rules are enforced and that + * any additional logic is executed such as sending notifications or calculating statistics. + * + * @see se.su.dsv.scipro.testdata how to add new populators + * @see se.su.dsv.scipro.testdata.TestDataPopulator + * @see se.su.dsv.scipro.testdata.BaseData + * @see se.su.dsv.scipro.testdata.Factory + */ +package se.su.dsv.scipro.testdata.populators; diff --git a/test-data/src/main/resources/META-INF/services/se.su.dsv.scipro.war.PluginConfiguration b/test-data/src/main/resources/META-INF/services/se.su.dsv.scipro.war.PluginConfiguration new file mode 100644 index 0000000000..99b97f03e4 --- /dev/null +++ b/test-data/src/main/resources/META-INF/services/se.su.dsv.scipro.war.PluginConfiguration @@ -0,0 +1 @@ +se.su.dsv.scipro.testdata.TestDataConfiguration diff --git a/war/pom.xml b/war/pom.xml index c1dd889587..6a347c9b09 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -140,5 +140,18 @@ <spring.profile.active>branch</spring.profile.active> </properties> </profile> + <profile> + <id>DEV</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <dependencies> + <dependency> + <groupId>se.su.dsv.scipro</groupId> + <artifactId>test-data</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + </profile> </profiles> </project>