diff --git a/src/lnp/java/teammates/lnp/sql/BaseLNPTestCase.java b/src/lnp/java/teammates/lnp/sql/BaseLNPTestCase.java new file mode 100644 index 00000000000..94cb0eb698b --- /dev/null +++ b/src/lnp/java/teammates/lnp/sql/BaseLNPTestCase.java @@ -0,0 +1,361 @@ +package teammates.lnp.sql; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; +import java.util.StringJoiner; + +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.report.config.ConfigurationException; +import org.apache.jmeter.report.dashboard.GenerationException; +import org.apache.jmeter.report.dashboard.ReportGenerator; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.reporters.Summariser; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonReader; + +import teammates.common.datatransfer.SqlDataBundle; +import teammates.common.exception.HttpRequestFailedException; +import teammates.common.util.JsonUtils; +import teammates.common.util.Logger; +import teammates.lnp.util.BackDoor; +import teammates.lnp.util.LNPResultsStatistics; +import teammates.lnp.util.LNPSpecification; +import teammates.lnp.util.LNPSqlTestData; +import teammates.lnp.util.TestProperties; +import teammates.test.BaseTestCase; +import teammates.test.FileHelper; + +/** + * Base class for all L&P test cases. + */ +public abstract class BaseLNPTestCase extends BaseTestCase { + + static final String GET = HttpGet.METHOD_NAME; + static final String POST = HttpPost.METHOD_NAME; + static final String PUT = HttpPut.METHOD_NAME; + static final String DELETE = HttpDelete.METHOD_NAME; + + private static final Logger log = Logger.getLogger(); + + private static final int RESULT_COUNT = 3; + + final BackDoor backdoor = BackDoor.getInstance(); + String timeStamp; + LNPSpecification specification; + + /** + * Returns the test data used for the current test. + */ + protected abstract LNPSqlTestData getTestData(); + + /** + * Returns the JMeter test plan for the L&P test case. + * @return A nested tree structure that consists of the various elements that are used in the JMeter test. + */ + protected abstract ListedHashTree getLnpTestPlan(); + + /** + * Sets up the specification for this L&P test case. + */ + protected abstract void setupSpecification(); + + /** + * Returns the path to the generated JSON data bundle file. + */ + protected String getJsonDataPath() { + return "/" + getClass().getSimpleName() + timeStamp + ".json"; + } + + /** + * Returns the path to the generated JMeter CSV config file. + */ + protected String getCsvConfigPath() { + return "/" + getClass().getSimpleName() + "Config" + timeStamp + ".csv"; + } + + /** + * Returns the path to the generated JTL test results file. + */ + protected String getJtlResultsPath() { + return "/" + getClass().getSimpleName() + timeStamp + ".jtl"; + } + + @Override + protected String getTestDataFolder() { + return TestProperties.LNP_TEST_DATA_FOLDER; + } + + /** + * Returns the path to the data file, relative to the project root directory. + */ + protected String getPathToTestDataFile(String fileName) { + return getTestDataFolder() + fileName; + } + + /** + * Returns the path to the JSON test results statistics file, relative to the project root directory. + */ + private String getPathToTestStatisticsResultsFile() { + return String.format("%s/%sStatistics%s.json", TestProperties.LNP_TEST_RESULTS_FOLDER, + this.getClass().getSimpleName(), this.timeStamp); + } + + String createFileAndDirectory(String directory, String fileName) throws IOException { + File dir = new File(directory); + if (!dir.exists()) { + dir.mkdir(); + } + + String pathToFile = directory + fileName; + File file = new File(pathToFile); + + // Write data to the file; overwrite if it already exists + if (file.exists()) { + file.delete(); + } + file.createNewFile(); + return pathToFile; + } + + /** + * Creates the JSON data and writes it to the file specified by {@link #getJsonDataPath()}. + */ + void createJsonDataFile(LNPSqlTestData testData) throws IOException { + SqlDataBundle jsonData = testData.generateJsonData(); + + String pathToResultFile = createFileAndDirectory(TestProperties.LNP_TEST_DATA_FOLDER, getJsonDataPath()); + try (BufferedWriter bw = Files.newBufferedWriter(Paths.get(pathToResultFile))) { + bw.write(JsonUtils.toJson(jsonData, SqlDataBundle.class)); + bw.flush(); + } + } + + /** + * Creates the CSV data and writes it to the file specified by {@link #getCsvConfigPath()}. + */ + private void createCsvConfigDataFile(LNPSqlTestData testData) throws IOException { + List headers = testData.generateCsvHeaders(); + List> valuesList = testData.generateCsvData(); + + String pathToCsvFile = createFileAndDirectory(TestProperties.LNP_TEST_DATA_FOLDER, getCsvConfigPath()); + try (BufferedWriter bw = Files.newBufferedWriter(Paths.get(pathToCsvFile))) { + // Write headers and data to the CSV file + bw.write(convertToCsv(headers)); + + for (List values : valuesList) { + bw.write(convertToCsv(values)); + } + + bw.flush(); + } + } + + /** + * Converts the list of {@code values} to a CSV row. + * @return A single string containing {@code values} separated by pipelines and ending with newline. + */ + String convertToCsv(List values) { + StringJoiner csvRow = new StringJoiner("|", "", "\n"); + for (String value : values) { + csvRow.add(value); + } + return csvRow.toString(); + } + + /** + * Returns the L&P test results statistics. + * @return The initialized result statistics from the L&P test results. + * @throws IOException if there is an error when loading the result file. + */ + private LNPResultsStatistics getResultsStatistics() throws IOException { + Gson gson = new Gson(); + JsonReader reader = new JsonReader(Files.newBufferedReader(Paths.get(getPathToTestStatisticsResultsFile()))); + JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); + + JsonObject endpointStats = jsonObject.getAsJsonObject("HTTP Request Sampler"); + return gson.fromJson(endpointStats, LNPResultsStatistics.class); + } + + /** + * Renames the default results statistics file to the name of the test. + */ + private void renameStatisticsFile() { + File defaultFile = new File(TestProperties.LNP_TEST_RESULTS_FOLDER + "/statistics.json"); + File lnpStatisticsFile = new File(getPathToTestStatisticsResultsFile()); + + if (lnpStatisticsFile.exists()) { + lnpStatisticsFile.delete(); + } + if (!defaultFile.renameTo(lnpStatisticsFile)) { + log.warning("Failed to rename generated statistics.json file."); + } + } + + /** + * Setup and load the JMeter configuration and property files to run the Jmeter test. + * @throws IOException if the save service properties file cannot be loaded. + */ + private void setJmeterProperties() throws IOException { + JMeterUtils.loadJMeterProperties(TestProperties.JMETER_PROPERTIES_PATH); + JMeterUtils.setJMeterHome(TestProperties.JMETER_HOME); + JMeterUtils.initLocale(); + SaveService.loadProperties(); + } + + /** + * Creates the JSON test data and CSV config data files for the performance test from {@code testData}. + */ + protected void createTestData() throws IOException, HttpRequestFailedException { + LNPSqlTestData testData = getTestData(); + createJsonDataFile(testData); + persistTestData(); + createCsvConfigDataFile(testData); + } + + /** + * Creates the entities in the database from the JSON data file. + */ + protected void persistTestData() throws IOException, HttpRequestFailedException { + SqlDataBundle dataBundle = loadSqlDataBundle(getJsonDataPath()); + SqlDataBundle responseBody = backdoor.removeAndRestoreSqlDataBundle(dataBundle); + + String pathToResultFile = createFileAndDirectory(TestProperties.LNP_TEST_DATA_FOLDER, getJsonDataPath()); + String jsonValue = JsonUtils.toJson(responseBody, SqlDataBundle.class); + FileHelper.saveFile(pathToResultFile, jsonValue); + } + + /** + * Display the L&P results on the console. + */ + protected void displayLnpResults() throws IOException { + LNPResultsStatistics resultsStats = getResultsStatistics(); + + resultsStats.displayLnpResultsStatistics(); + specification.verifyLnpTestSuccess(resultsStats); + } + + /** + * Runs the JMeter test. + * @param shouldCreateJmxFile true if the generated test plan should be saved to a `.jmx` file which + * can be opened in the JMeter GUI, and false otherwise. + */ + protected void runJmeter(boolean shouldCreateJmxFile) throws IOException { + StandardJMeterEngine jmeter = new StandardJMeterEngine(); + setJmeterProperties(); + + HashTree testPlan = getLnpTestPlan(); + + if (shouldCreateJmxFile) { + String pathToConfigFile = createFileAndDirectory( + TestProperties.LNP_TEST_CONFIG_FOLDER, "/" + getClass().getSimpleName() + ".jmx"); + SaveService.saveTree(testPlan, Files.newOutputStream(Paths.get(pathToConfigFile))); + } + + // Add result collector to the test plan for generating results file + Summariser summariser = null; + String summariserName = JMeterUtils.getPropDefault("summariser.name", "summary"); + if (summariserName.length() > 0) { + summariser = new Summariser(summariserName); + } + + String resultsFile = createFileAndDirectory(TestProperties.LNP_TEST_RESULTS_FOLDER, getJtlResultsPath()); + ResultCollector resultCollector = new ResultCollector(summariser); + resultCollector.setFilename(resultsFile); + testPlan.add(testPlan.getArray()[0], resultCollector); + + // Run Jmeter Test + jmeter.configure(testPlan); + jmeter.run(); + + try { + ReportGenerator reportGenerator = new ReportGenerator(resultsFile, null); + reportGenerator.generate(); + } catch (ConfigurationException | GenerationException e) { + log.warning(e.getMessage()); + } + + renameStatisticsFile(); + } + + /** + * Deletes the data that was created in the database from the JSON data file. + */ + protected void deleteTestData() { + SqlDataBundle dataBundle = loadSqlDataBundle(getJsonDataPath()); + backdoor.removeSqlDataBundle(dataBundle); + } + + /** + * Deletes the JSON and CSV data files that were created. + */ + protected void deleteDataFiles() throws IOException { + String pathToJsonFile = getPathToTestDataFile(getJsonDataPath()); + String pathToCsvFile = getPathToTestDataFile(getCsvConfigPath()); + + Files.delete(Paths.get(pathToJsonFile)); + Files.delete(Paths.get(pathToCsvFile)); + } + + /** + * Deletes the oldest excess result .jtl file and the statistics file, if there are more than RESULT_COUNT. + */ + protected void cleanupResults() throws IOException { + File[] fileList = new File(TestProperties.LNP_TEST_RESULTS_FOLDER) + .listFiles((d, s) -> { + return s.contains(this.getClass().getSimpleName()); + }); + if (fileList == null) { + fileList = new File[] {}; + } + Arrays.sort(fileList, (a, b) -> { + return b.getName().compareTo(a.getName()); + }); + + int jtlCounter = 0; + int statisticsCounter = 0; + for (File file : fileList) { + if (file.getName().contains("Statistics")) { + statisticsCounter++; + if (statisticsCounter > RESULT_COUNT) { + Files.delete(file.toPath()); + } + } else { + jtlCounter++; + if (jtlCounter > RESULT_COUNT) { + Files.delete(file.toPath()); + } + } + } + } + + /** + * Sanitize the string to be CSV-safe string. + */ + protected String sanitizeForCsv(String originalString) { + return String.format("\"%s\"", originalString.replace(System.lineSeparator(), "").replace("\"", "\"\"")); + } + + /** + * Generates timestamp for generated statistics/CSV files in order to prevent concurrency issues. + */ + protected void generateTimeStamp() { + this.timeStamp = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("_uuuuMMddHHmmss")); + } +} diff --git a/src/lnp/java/teammates/lnp/sql/InstructorCourseUpdateLNPTest.java b/src/lnp/java/teammates/lnp/sql/InstructorCourseUpdateLNPTest.java new file mode 100644 index 00000000000..6196fd1f916 --- /dev/null +++ b/src/lnp/java/teammates/lnp/sql/InstructorCourseUpdateLNPTest.java @@ -0,0 +1,216 @@ +package teammates.lnp.sql; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jorphan.collections.HashTree; +import org.apache.jorphan.collections.ListedHashTree; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import teammates.common.datatransfer.InstructorPermissionRole; +import teammates.common.datatransfer.InstructorPrivileges; +import teammates.common.datatransfer.SqlDataBundle; +import teammates.common.exception.HttpRequestFailedException; +import teammates.common.util.Const; +import teammates.common.util.JsonUtils; +import teammates.lnp.util.JMeterElements; +import teammates.lnp.util.LNPSpecification; +import teammates.lnp.util.LNPSqlTestData; +import teammates.storage.sqlentity.Account; +import teammates.storage.sqlentity.Course; +import teammates.storage.sqlentity.FeedbackSession; +import teammates.storage.sqlentity.Instructor; +import teammates.ui.request.CourseUpdateRequest; + +/** + * L&P Test Case for course update cascade API. + */ +public class InstructorCourseUpdateLNPTest extends BaseLNPTestCase { + private static final int NUM_INSTRUCTORS = 1; + private static final int RAMP_UP_PERIOD = NUM_INSTRUCTORS * 2; + + private static final int NUM_FEEDBACK_SESSIONS = 500; + + private static final String COURSE_ID = "TestData.CS101"; + private static final String COURSE_NAME = "LnPCourse"; + private static final String COURSE_TIME_ZONE = "UTC"; + private static final String COURSE_INSTITUTE = "LnpInstitute"; + + private static final String ACCOUNT_NAME = "LnpAccount"; + + private static final String UPDATE_COURSE_NAME = "updatedCourse"; + private static final String UPDATE_COURSE_TIME_ZONE = "GMT"; + + private static final String INSTRUCTOR_ID = "LnPInstructor_id"; + private static final String INSTRUCTOR_NAME = "LnPInstructor"; + private static final String INSTRUCTOR_EMAIL = "tmms.test@gmail.tmt"; + + private static final String FEEDBACK_SESSION_NAME = "Test Feedback Session"; + + private static final double ERROR_RATE_LIMIT = 0.01; + private static final double MEAN_RESP_TIME_LIMIT = 10; + + @Override + protected LNPSqlTestData getTestData() { + Account instructorAccount = new Account(INSTRUCTOR_ID, ACCOUNT_NAME, INSTRUCTOR_EMAIL); + Course instructorCourse = new Course(COURSE_ID, COURSE_NAME, COURSE_TIME_ZONE, COURSE_INSTITUTE); + return new LNPSqlTestData() { + @Override + protected Map generateCourses() { + Map courses = new HashMap<>(); + + courses.put(COURSE_NAME, instructorCourse); + + return courses; + } + + @Override + protected Map generateAccounts() { + Map accounts = new HashMap<>(); + + accounts.put(ACCOUNT_NAME, instructorAccount); + + return accounts; + } + + @Override + protected Map generateInstructors() { + Map instructors = new HashMap<>(); + + Instructor instructor = new Instructor( + instructorCourse, INSTRUCTOR_NAME, INSTRUCTOR_EMAIL, + true, "Co-owner", InstructorPermissionRole.INSTRUCTOR_PERMISSION_ROLE_COOWNER, + new InstructorPrivileges(Const.InstructorPermissionRoleNames.INSTRUCTOR_PERMISSION_ROLE_COOWNER)); + + instructor.setAccount(instructorAccount); + instructors.put(INSTRUCTOR_NAME, instructor); + + return instructors; + } + + @Override + protected Map generateFeedbackSessions() { + Map feedbackSessions = new LinkedHashMap<>(); + + for (int i = 1; i <= NUM_FEEDBACK_SESSIONS; i++) { + Instant now = Instant.now(); + FeedbackSession session = new FeedbackSession(FEEDBACK_SESSION_NAME + " " + i, + instructorCourse, INSTRUCTOR_EMAIL, "", + now.plus(Duration.ofMinutes(1)), now.plus(Duration.ofDays(1)), + now, now.plus(Duration.ofDays(2)), null, false, false, false); + + feedbackSessions.put(FEEDBACK_SESSION_NAME + " " + i, session); + } + + return feedbackSessions; + } + + @Override + public List generateCsvHeaders() { + List headers = new ArrayList<>(); + + headers.add("loginId"); + headers.add("courseId"); + headers.add("updateData"); + + return headers; + } + + @Override + public List> generateCsvData() { + SqlDataBundle dataBundle = loadSqlDataBundle(getJsonDataPath()); + List> csvData = new ArrayList<>(); + + dataBundle.instructors.forEach((key, instructor) -> { + List csvRow = new ArrayList<>(); + + csvRow.add(INSTRUCTOR_ID); + csvRow.add(COURSE_ID); + + CourseUpdateRequest courseUpdateRequest = new CourseUpdateRequest(); + courseUpdateRequest.setCourseName(UPDATE_COURSE_NAME); + courseUpdateRequest.setTimeZone(UPDATE_COURSE_TIME_ZONE); + + String updateData = sanitizeForCsv(JsonUtils.toJson(courseUpdateRequest)); + csvRow.add(updateData); + + csvData.add(csvRow); + }); + + return csvData; + } + }; + } + + private Map getRequestHeaders() { + Map headers = new HashMap<>(); + + headers.put(Const.HeaderNames.CSRF_TOKEN, "${csrfToken}"); + headers.put("Content-Type", "application/json"); + + return headers; + } + + private String getTestEndpoint() { + return Const.ResourceURIs.COURSE + "?courseid=${courseId}"; + } + + @Override + protected ListedHashTree getLnpTestPlan() { + ListedHashTree testPlan = new ListedHashTree(JMeterElements.testPlan()); + HashTree threadGroup = testPlan.add( + JMeterElements.threadGroup(NUM_INSTRUCTORS, RAMP_UP_PERIOD, 1)); + + threadGroup.add(JMeterElements.csvDataSet(getPathToTestDataFile(getCsvConfigPath()))); + threadGroup.add(JMeterElements.cookieManager()); + threadGroup.add(JMeterElements.defaultSampler()); + + threadGroup.add(JMeterElements.onceOnlyController()) + .add(JMeterElements.loginSampler()) + .add(JMeterElements.csrfExtractor("csrfToken")); + + // Add HTTP sampler for test endpoint + HeaderManager headerManager = JMeterElements.headerManager(getRequestHeaders()); + threadGroup.add(JMeterElements.httpSampler(getTestEndpoint(), PUT, "${updateData}")) + .add(headerManager); + + return testPlan; + } + + @Override + protected void setupSpecification() { + this.specification = LNPSpecification.builder() + .withErrorRateLimit(ERROR_RATE_LIMIT) + .withMeanRespTimeLimit(MEAN_RESP_TIME_LIMIT) + .build(); + } + + @BeforeClass + public void classSetup() throws IOException, HttpRequestFailedException { + generateTimeStamp(); + createTestData(); + setupSpecification(); + } + + @Test + public void runLnpTest() throws IOException { + runJmeter(false); + displayLnpResults(); + } + + @AfterClass + public void classTearDown() throws IOException { + deleteTestData(); + deleteDataFiles(); + cleanupResults(); + } +} diff --git a/src/lnp/java/teammates/lnp/sql/package-info.java b/src/lnp/java/teammates/lnp/sql/package-info.java new file mode 100644 index 00000000000..bf87f3a9fe8 --- /dev/null +++ b/src/lnp/java/teammates/lnp/sql/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains L&P test cases. + */ +package teammates.lnp.sql; diff --git a/src/lnp/java/teammates/lnp/util/LNPSqlTestData.java b/src/lnp/java/teammates/lnp/util/LNPSqlTestData.java new file mode 100644 index 00000000000..48aebf22f2d --- /dev/null +++ b/src/lnp/java/teammates/lnp/util/LNPSqlTestData.java @@ -0,0 +1,92 @@ +package teammates.lnp.util; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import teammates.common.datatransfer.SqlDataBundle; +import teammates.storage.sqlentity.Account; +import teammates.storage.sqlentity.Course; +import teammates.storage.sqlentity.FeedbackQuestion; +import teammates.storage.sqlentity.FeedbackResponse; +import teammates.storage.sqlentity.FeedbackResponseComment; +import teammates.storage.sqlentity.FeedbackSession; +import teammates.storage.sqlentity.Instructor; +import teammates.storage.sqlentity.Student; + +/** + * L&P test data generator. + */ +public abstract class LNPSqlTestData { + + // CHECKSTYLE.OFF:MissingJavadocMethod generator for different entities are self-explained by the method name + + protected Map generateAccounts() { + return new HashMap<>(); + } + + protected Map generateCourses() { + return new HashMap<>(); + } + + protected Map generateInstructors() { + return new HashMap<>(); + } + + protected Map generateStudents() { + return new HashMap<>(); + } + + protected Map generateFeedbackSessions() { + return new HashMap<>(); + } + + protected Map generateFeedbackQuestions() { + return new HashMap<>(); + } + + protected Map generateFeedbackResponses() { + return new HashMap<>(); + } + + protected Map generateFeedbackResponseComments() { + return new HashMap<>(); + } + + // CHECKSTYLE.ON:MissingJavadocMethod + + /** + * Returns a JSON data bundle containing the data relevant for the performance test. + */ + public SqlDataBundle generateJsonData() { + SqlDataBundle dataBundle = new SqlDataBundle(); + + dataBundle.accounts = generateAccounts(); + dataBundle.courses = generateCourses(); + dataBundle.instructors = generateInstructors(); + dataBundle.students = generateStudents(); + dataBundle.feedbackSessions = generateFeedbackSessions(); + dataBundle.feedbackQuestions = generateFeedbackQuestions(); + dataBundle.feedbackResponses = generateFeedbackResponses(); + dataBundle.feedbackResponseComments = generateFeedbackResponseComments(); + + return dataBundle; + } + + /** + * Returns list of header fields for the data in the CSV file to be generated. + * + *

Note that these header names should correspond to the variables used in the JMeter L&P test.

+ */ + public abstract List generateCsvHeaders(); + + /** + * Returns the data for the entries in the CSV file to be generated. + * The order of the field values for each entry should correspond to the order of headers specified + * in {@link #generateCsvHeaders()}. + * + * @return List of entries, which are made up of a list of field values. + */ + public abstract List> generateCsvData(); + +} diff --git a/src/test/java/teammates/architecture/ArchitectureTest.java b/src/test/java/teammates/architecture/ArchitectureTest.java index 1113db61372..83d1221a6e2 100644 --- a/src/test/java/teammates/architecture/ArchitectureTest.java +++ b/src/test/java/teammates/architecture/ArchitectureTest.java @@ -49,6 +49,7 @@ public class ArchitectureTest { private static final String LNP_PACKAGE = "teammates.lnp"; private static final String LNP_CASES_PACKAGE = LNP_PACKAGE + ".cases"; + private static final String LNP_SQL_PACKAGE = LNP_PACKAGE + ".sql"; private static final String LNP_UTIL_PACKAGE = LNP_PACKAGE + ".util"; private static final String CLIENT_PACKAGE = "teammates.client"; @@ -413,7 +414,13 @@ public void testArchitecture_lnp_lnpShouldBeSelfContained() { @Test public void testArchitecture_lnp_lnpShouldNotTouchProductionCodeExceptCommonAndRequests() { noClasses().that().resideInAPackage(includeSubpackages(LNP_PACKAGE)) - .should().accessClassesThat().resideInAPackage(includeSubpackages(STORAGE_PACKAGE)) + .should().accessClassesThat(new DescribedPredicate<>("") { + @Override + public boolean apply(JavaClass input) { + return input.getPackageName().startsWith(STORAGE_PACKAGE) + && !input.getPackageName().startsWith(STORAGE_SQL_ENTITY_PACKAGE); + } + }) .orShould().accessClassesThat().resideInAPackage(includeSubpackages(LOGIC_PACKAGE)) .orShould().accessClassesThat(new DescribedPredicate<>("") { @Override @@ -442,6 +449,23 @@ public boolean apply(JavaClass input) { }).check(forClasses(LNP_CASES_PACKAGE)); } + @Test + public void testArchitecture_lnp_lnpSqlTestCasesShouldBeIndependentOfEachOther() { + noClasses().that(new DescribedPredicate<>("") { + @Override + public boolean apply(JavaClass input) { + return input.getPackageName().startsWith(LNP_SQL_PACKAGE) && !input.isInnerClass(); + } + }).should().accessClassesThat(new DescribedPredicate<>("") { + @Override + public boolean apply(JavaClass input) { + return input.getPackageName().startsWith(LNP_SQL_PACKAGE) + && !input.getSimpleName().startsWith("Base") + && !input.isInnerClass(); + } + }).check(forClasses(LNP_SQL_PACKAGE)); + } + @Test public void testArchitecture_lnp_lnpShouldNotHaveAnyDependency() { noClasses().that().resideInAPackage(includeSubpackages(LNP_UTIL_PACKAGE))