Fakultas Ilmu Komputer UI

Skip to content
Snippets Groups Projects
Commit 82460980 authored by Daya Adianto's avatar Daya Adianto
Browse files

Set up the code for SQA course year 2023

parent a030418f
No related branches found
Tags 0.1.1
1 merge request!6Set up the code for SQA course year 2023
Pipeline #184282 passed with stages
in 3 minutes and 54 seconds
Showing
with 521 additions and 130 deletions
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### Eclipse ###
# Source: https://github.com/github/gitignore/blob/main/Global/Eclipse.gitignore
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
### IntelliJ IDEA ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# Source: https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
*.iml
*.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Java ###
# Source: https://github.com/github/gitignore/blob/main/Java.gitignore
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# Custom ignore list
.venv/
# Static site
site/
# H2 database file
sitodo.db*
sitodo.*.db
# Project-specific file
.editorconfig
.env
.gitlab-ci.yml
docker-compose.yml
Dockerfile
LICENSE
README.md
......@@ -25,3 +25,7 @@ end_of_line = crlf
[*.xml]
indent_style = tab
[*.java]
ij_java_align_multiline_chained_methods = true
ij_java_method_call_chain_wrap = split_into_lines
\ No newline at end of file
......@@ -199,6 +199,7 @@ hs_err_pid*
replay_pid*
# Custom ignore list
.env
.venv/
# Static site
......
---
# Based on the Maven CI/CD template from GitLab: https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
services:
- postgres:14-alpine
variables:
# This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
......@@ -22,10 +20,6 @@ variables:
-DinstallAtEnd=true
-DdeployAtEnd=true
SAST_JAVA_VERSION: 17
POSTGRES_DB: sitodo
POSTGRES_USER: postgres
POSTGRES_PASSWORD: testpass
POSTGRES_HOST_AUTH_METHOD: trust
# TODO: Add Code Quality and SAST job templates into the CI/CD pipeline configuration
# Check the list of available templates at https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates
......@@ -34,15 +28,26 @@ include:
- template: Workflows/Branch-Pipelines.gitlab-ci.yml
# TODO: Create .codeclimate.yml file to configure the static analysis engine (i.e. Code Climate) used by Code Quality CI job
stages:
- build
- test
- deploy
- post-deploy
- report
.upstream-deploy-production-rules:
rules:
- if: $CI_PROJECT_NAMESPACE != "pmpl/examples/sitodo-pmpl"
when: never
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: always
build:
stage: build
image: docker.io/library/maven:3.8.6-eclipse-temurin-17-focal
before_script:
- java -version && javac --version && mvn --version
- pwd
script:
- mvn $MAVEN_CLI_OPTS -DskipTests
-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository package
......@@ -50,24 +55,38 @@ build:
key:
files:
- pom.xml
prefix: $CI_JOB_NAME
paths:
- .m2/repository
artifacts:
paths:
- .m2/
- target/
# TODO: Complete the following 'test' CI job to run all test suites in the project (i.e. unit test, functional test)
test:
stage: test
image: docker.io/library/maven:3.8.6-eclipse-temurin-17-focal
services:
- name: docker.io/library/postgres:14-alpine
alias: database
variables:
SPRING_DATASOURCE_URL: jdbc:postgresql://database:5432/sitodo
SPRING_DATASOURCE_USERNAME: sitodo
SPRING_DATASOURCE_PASSWORD: sitodo_cicd
POSTGRES_USER: sitodo
POSTGRES_PASSWORD: sitodo_cicd
needs:
- build
before_script:
- apt-get update && apt-get install -y firefox
- java -version && javac --version && mvn --version
- pwd
script:
# Run test suites
- mvn clean test
- cat target/site/jacoco/index.html
needs: []
# Generate test reports
- mvn verify -DskipTests
# Get line coverage
- grep -o "Total[^%]*%" target/site/jacoco/index.html
coverage: '/Total.*?(\d{1,3})%/'
cache:
key:
files:
......@@ -76,12 +95,66 @@ test:
- .m2/repository
artifacts:
paths:
- target/surefire-reports
- target/*.exec
- target/site/jacoco/
reports:
junit:
- target/surefire-reports/TEST-*.xml
# TODO: Deploy the project to Heroku or other PaaS of your choice (e.g. Fly.io)
deploy:
stage: deploy
image: docker.io/flyio/flyctl:v0.1.103
variables:
FLY_API_TOKEN: $PRODUCTION_FLY_API_TOKEN
rules:
- !reference [.upstream-deploy-production-rules, rules]
script:
- flyctl deploy --remote-only
environment:
name: production
url: https://sitodo-pmpl.fly.dev
# TODO: Run only the BDD test suite, excluding unit and functional tests
bdd-test:
stage: post-deploy
image: docker.io/library/maven:3.8.6-eclipse-temurin-17-focal
services:
- name: docker.io/library/postgres:14-alpine
alias: database
variables:
SPRING_DATASOURCE_URL: jdbc:postgresql://database:5432/sitodo
SPRING_DATASOURCE_USERNAME: sitodo
SPRING_DATASOURCE_PASSWORD: sitodo_cicd
POSTGRES_USER: sitodo
POSTGRES_PASSWORD: sitodo_cicd
rules:
- !reference [.upstream-deploy-production-rules, rules]
- allow_failure: true
needs:
- build
- deploy
before_script:
- apt-get update && apt-get install -y firefox
- sed -i "s#headless.mode = false#headless.mode = true#" src/test/resources/serenity.conf
- sed -i "s#http://localhost:8080#https://sitodo-pmpl.fly.dev#" src/test/resources/serenity.conf
script:
- mvn clean verify
artifacts:
paths:
- target/site/serenity
visualize-coverage:
stage: report
image: registry.gitlab.com/haynes/jacoco2cobertura:1.0.9
before_script: []
script:
- python /opt/cover2cover.py target/site/jacoco/jacoco.xml $CI_PROJECT_DIR/src/main/java > target/cobertura.xml
needs:
- test
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: target/cobertura.xml
# TODO: Configure 'spotbugs-sast' job (part of SAST job template) so it analyses the compiled Java artifacts
# Use JDK & Maven image to build the application
FROM docker.io/library/maven:3.9.4-eclipse-temurin-17-alpine AS builder
# Set the working directory inside the container
WORKDIR /src
# Copy the source code into the container
COPY . .
# Build the application JAR file
RUN mvn -DskipTests package
# Use JRE image for running the application
FROM docker.io/library/eclipse-temurin:17.0.8.1_1-jre-alpine
# Create a non-root user named "app" to own and run the application
RUN addgroup app \
&& adduser -s /bin/false -G app -D -H app
# Switch to the "app" user, so the application does not run as root
USER app
# Set the working directory inside the container to /opt/app
WORKDIR /opt/app
# Copy the app into the container
COPY --chown=app:app --from=builder /src/target/sitodo-*.jar .
# Expose port 8080
EXPOSE 8080
# Run the application JAR file
CMD ["/bin/sh", "-c", "java -jar sitodo-*.jar"]
MIT License
Copyright (c) 2022 Daya Adianto
Copyright (c) 2023 Daya Adianto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
# Sitodo (PMPL Variant)
> TODO: Create badges that display pipeline status and code coverage percentage
[![pipeline status](https://gitlab.cs.ui.ac.id/pmpl/examples/sitodo-pmpl/badges/main/pipeline.svg)](https://gitlab.cs.ui.ac.id/pmpl/examples/sitodo-pmpl/-/commits/main)
[![coverage report](https://gitlab.cs.ui.ac.id/pmpl/examples/sitodo-pmpl/badges/main/coverage.svg)](https://gitlab.cs.ui.ac.id/pmpl/examples/sitodo-pmpl/-/commits/main)
A basic todo app project for teaching basic Web programming, Git workflows, and
CI/CD. Heavily inspired by the running example in "Test-Driven Development with
......@@ -76,6 +77,11 @@ You can customise the configuration by providing an `application.properties`
file in the same directory as the executable JAR file. See the built-in
configuration in the [source code](./src/main/resources/application.properties).
You can also set the configuration during runtime using environment variables.
Following Spring Boot convention, properties are named in all uppercase, and dot separators are replaced with underscores.
For instance, `spring.datasource.url` becomes `SPRING_DATASOURCE_URL` when configured using an environment variable.
See the example in the [GitLab CI/CD configuration](./.gitlab-ci.yml), specifically in the job for running tests.
## Practical Tasks
See the TODO items spread across several files in the project:
......@@ -88,15 +94,7 @@ grep -nr "TODO:"
## Running Example
See the running example based on the main branch at [Heroku](https://sitodo-example.herokuapp.com).
## DB_* Variables Needed for Running The Test in Gitlab
The following DB variables need to be defined in Gitlab Environment Variable in order run the test in Gitlab:
- DB_HOST
- DB_NAME
- DB_PASSWORD
- DB_PORT
- DB_USER
See the running example based on the main branch at [Fly.io](https://sitodo-pmpl.fly.dev).
## License
......@@ -105,4 +103,5 @@ This project is licensed under the terms of the [MIT license](./LICENSE).
## Exercise Report
> TODO: (For SQA/PMPL course participants) Write the URL to your deployed application in this section.
>
> TODO: (For SQA/PMPL course participants) Write your report in this section.
......@@ -4,7 +4,14 @@ services:
database:
image: docker.io/library/postgres:14.5-alpine3.16
ports:
- "5432:5432"
- "127.0.0.1:5432:5432"
environment:
POSTGRES_USER: sitodo
POSTGRES_PASSWORD: R83Moz74
pgadmin:
image: docker.io/dpage/pgadmin4:7.7
ports:
- "127.0.0.1:8081:80"
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: R83Moz74
fly.toml 0 → 100644
# fly.toml app configuration file generated for sitodo-pmpl on 2023-09-27T14:44:21+07:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = "sitodo-pmpl"
primary_region = "sin"
swap_size = 512
[build]
dockerfile = "Dockerfile"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ["app"]
[env]
DEBUG = "false"
TRACE = "false"
......@@ -5,21 +5,20 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<version>3.1.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>sitodo</artifactId>
<version>0.1.0-SNAPSHOT</version>
<name>sitodo</name>
<version>0.1.1-SNAPSHOT</version>
<name>sitodo-pmpl</name>
<description>A basic todo app for teaching basic Web programming, Git workflows, and CI/CD. Heavily inspired by the
running example in &quot;Test-Driven Development with Python&quot; book by Harry Percival.
</description>
<properties>
<java.version>17</java.version>
<serenity.version>3.4.2</serenity.version>
<selenium.version>4.6.0</selenium.version>
<tags></tags>
<serenity.version>4.0.12</serenity.version>
<selenium.version>4.12.1</selenium.version>
</properties>
<dependencies>
<dependency>
......@@ -30,90 +29,76 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>bootstrap</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.codeborne</groupId>
<artifactId>selenide</artifactId>
<version>6.7.4</version>
<version>6.18.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-core</artifactId>
<version>${serenity.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-cucumber</artifactId>
<version>${serenity.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-screenplay</artifactId>
<version>${serenity.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-screenplay-webdriver</artifactId>
<version>${serenity.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-ensure</artifactId>
<version>${serenity.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-spring</artifactId>
<version>${serenity.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
......@@ -151,7 +136,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<version>0.8.10</version>
<executions>
<execution>
<id>default-prepare-agent</id>
......@@ -196,22 +181,27 @@
</execution>
</executions>
</plugin>
<!-- Use Surefire to run unit & functional test suites -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<version>3.1.2</version>
<configuration>
<skip>false</skip>
<excludes>
<exclude>**/*TestSuite.java</exclude>
</excludes>
</configuration>
</plugin>
<!-- Use Failsafe to run the BDD test suites -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.0.0-M7</version>
<version>3.1.2</version>
<configuration>
<includes>
<include>**/*TestSuite.java</include>
</includes>
<excludedGroups>unit, e2e</excludedGroups>
<parallel>classes</parallel>
<parallel>methods</parallel>
<useUnlimitedThreads>true</useUnlimitedThreads>
......@@ -244,5 +234,4 @@
</plugin>
</plugins>
</build>
</project>
......@@ -3,7 +3,7 @@ package com.example.sitodo.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import jakarta.persistence.*;
@Data
@Entity
......@@ -11,7 +11,8 @@ import javax.persistence.*;
public class TodoItem {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "todo_item_seq_gen")
@SequenceGenerator(name = "todo_item_seq_gen", sequenceName = "todo_item_seq", allocationSize = 1)
private Long id;
@Column(nullable = false)
......
......@@ -3,7 +3,7 @@ package com.example.sitodo.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import jakarta.persistence.*;
import java.util.List;
@Data
......@@ -12,7 +12,8 @@ import java.util.List;
public class TodoList {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "todo_list_seq_gen")
@SequenceGenerator(name = "todo_list_seq_gen", sequenceName = "todo_list_seq", allocationSize = 1)
private Long id;
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
......
......@@ -5,12 +5,17 @@ databaseChangeLog:
author: sitodo_maintainer
changes:
- createSequence:
sequenceName: hibernate_sequence
sequenceName: todo_item_seq
dataType: INTEGER
incrementBy: 1
startValue: 1
minValue: 1
- createSequence:
sequenceName: todo_list_seq
dataType: INTEGER
incrementBy: 1
startValue: 1
minValue: 1
- createTable:
tableName: todo_item
columns:
......@@ -20,7 +25,7 @@ databaseChangeLog:
# from java.sql package: https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/Types.html
type: INTEGER
autoIncrement: true
valueSequenceNext: hibernate_sequence
valueSequenceNext: todo_item_seq
constraints:
primaryKey: true
primaryKeyName: pk_todo_item
......@@ -40,7 +45,7 @@ databaseChangeLog:
name: id
type: INTEGER
autoIncrement: true
valueSequenceNext: hibernate_sequence
valueSequenceNext: todo_list_seq
constraints:
primaryKey: true
primaryKeyName: pk_todo_list
......@@ -68,3 +73,7 @@ databaseChangeLog:
tableName: todo_list
- dropTable:
tableName: todo_item
- dropSequence:
sequenceName: todo_list_seq
- dropSequence:
sequenceName: todo_item_seq
......@@ -8,11 +8,13 @@ import net.serenitybdd.screenplay.actions.Enter;
public class AddAnItem {
public static Performable withName(String itemName) {
return Task.where("{0} adds an item with name " + itemName,
Enter.theValue(itemName)
.into(TodoListPage.ITEM_NAME_FIELD)
).then(Task.where("{0} clicks the enter button",
Click.on(TodoListPage.ENTER_BUTTON)
));
return Task
.where("{0} adds an item with name " + itemName,
Enter.theValue(itemName)
.into(TodoListPage.ITEM_NAME_FIELD)
)
.then(Task.where("{0} clicks the enter button",
Click.on(TodoListPage.ENTER_BUTTON)
));
}
}
......@@ -7,6 +7,8 @@ import net.serenitybdd.screenplay.actions.Open;
public class NavigateTo {
public static Performable theTodoListPage() {
return Task.where("{0} opens the Todo list page", Open.browserOn().the(TodoListPage.class));
return Task
.where("{0} opens the Todo list page", Open.browserOn()
.the(TodoListPage.class));
}
}
......@@ -16,5 +16,5 @@ public class TodoListPage extends PageObject {
public static Target ITEMS_LIST = Target
.the("item list")
.locatedBy("//tbody/tr/td[contains(@class, 'todo-item-title')]");
.located(By.xpath("//tbody/tr/td[contains(@class, 'todo-item-title')]"));
}
......@@ -9,6 +9,8 @@ import io.cucumber.java.en.When;
import net.serenitybdd.screenplay.Actor;
import net.serenitybdd.screenplay.ensure.Ensure;
import java.util.List;
public class AddItemStepDefinitions {
@Given("{actor} is looking at her TODO list")
......@@ -23,8 +25,12 @@ public class AddItemStepDefinitions {
@Then("{actor} sees {string} as an item in the TODO list")
public void she_sees_as_an_item_in_the_todo_list(Actor actor, String expectedItemName) {
List<String> todoItems = TodoListPage.ITEMS_LIST.resolveAllFor(actor)
.textContents();
actor.attemptsTo(
Ensure.that(TodoListPage.ITEMS_LIST).hasTextContent(expectedItemName)
Ensure.that(todoItems)
.contains(expectedItemName)
);
}
}
......@@ -8,7 +8,8 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(MainController.class)
@Tag("unit")
......@@ -20,9 +21,10 @@ class MainControllerTest {
@Test
@DisplayName("HTTP GET '/' redirects to '/list")
void showMainPage_resolvesToIndex() throws Exception {
mockMvc.perform(get("/")).andExpectAll(
status().is3xxRedirection(),
redirectedUrl("/list")
);
mockMvc.perform(get("/"))
.andExpectAll(
status().is3xxRedirection(),
redirectedUrl("/list")
);
}
}
......@@ -15,7 +15,7 @@ import java.util.List;
import java.util.NoSuchElementException;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
......@@ -37,23 +37,25 @@ class TodoListControllerTest {
@Test
@DisplayName("HTTP GET '/list' retrieves list view")
void showList_resolvesToIndex() throws Exception {
mockMvc.perform(get("/list")).andExpectAll(
status().isOk(),
content().contentTypeCompatibleWith(TEXT_HTML),
content().encoding(UTF_8),
view().name("list")
);
mockMvc.perform(get("/list"))
.andExpectAll(
status().isOk(),
content().contentTypeCompatibleWith(TEXT_HTML),
content().encoding(UTF_8),
view().name("list")
);
}
@Test
@DisplayName("HTTP GET '/list' returns an HTML page")
void showList_returnsHtml() throws Exception {
mockMvc.perform(get("/list")).andExpectAll(
status().isOk(),
content().contentTypeCompatibleWith(TEXT_HTML),
content().encoding(UTF_8),
content().string(containsString("</html>"))
);
mockMvc.perform(get("/list"))
.andExpectAll(
status().isOk(),
content().contentTypeCompatibleWith(TEXT_HTML),
content().encoding(UTF_8),
content().string(containsString("</html>"))
);
}
@Test
......@@ -65,15 +67,16 @@ class TodoListControllerTest {
when(mockList.getItems()).thenReturn(List.of(mockTodoItem));
when(todoListService.getTodoListById(anyLong())).thenReturn(mockList);
mockMvc.perform(get("/list/1")).andExpectAll(
status().isOk(),
content().contentTypeCompatibleWith(TEXT_HTML),
content().encoding(UTF_8),
content().string(containsString("<table")),
content().string(containsString("<tr")),
content().string(containsString("Buy milk")),
content().string(containsString("</html>"))
);
mockMvc.perform(get("/list/1"))
.andExpectAll(
status().isOk(),
content().contentTypeCompatibleWith(TEXT_HTML),
content().encoding(UTF_8),
content().string(containsString("<table")),
content().string(containsString("<tr")),
content().string(containsString("Buy milk")),
content().string(containsString("</html>"))
);
}
@Test
......@@ -81,9 +84,10 @@ class TodoListControllerTest {
void showList_byId_notFound() throws Exception {
when(todoListService.getTodoListById(anyLong())).thenThrow(NoSuchElementException.class);
mockMvc.perform(get("/list/1")).andExpectAll(
content().string(containsString("Not Found"))
);
mockMvc.perform(get("/list/1"))
.andExpectAll(
content().string(containsString("Not Found"))
);
}
@Test
......@@ -95,23 +99,25 @@ class TodoListControllerTest {
when(todoListService.updateTodoItem(anyLong(), anyLong(), anyBoolean())).thenReturn(mockList);
mockMvc.perform(get("/list/1/update/1?finished=true")).andExpectAll(
status().is3xxRedirection(),
redirectedUrl("/list/1")
);
mockMvc.perform(get("/list/1/update/1?finished=true"))
.andExpectAll(
status().is3xxRedirection(),
redirectedUrl("/list/1")
);
when(todoListService.getTodoListById(anyLong())).thenReturn(mockList);
mockMvc.perform(get("/list/1")).andExpectAll(
status().isOk(),
content().contentTypeCompatibleWith(TEXT_HTML),
content().encoding(UTF_8),
content().string(containsString("<table")),
content().string(containsString("<tr")),
content().string(containsString("Buy milk")),
content().string(containsString("Finished")),
content().string(containsString("</html>"))
);
mockMvc.perform(get("/list/1"))
.andExpectAll(
status().isOk(),
content().contentTypeCompatibleWith(TEXT_HTML),
content().encoding(UTF_8),
content().string(containsString("<table")),
content().string(containsString("<tr")),
content().string(containsString("Buy milk")),
content().string(containsString("Finished")),
content().string(containsString("</html>"))
);
// Note: Notice that we don't actually verify whether the item was successfully
// updated. It is all pre-scripted in the mock object. We dictate how the SUT
......@@ -156,7 +162,6 @@ class TodoListControllerTest {
);
}
private TodoList createMockTodoList(Long id, TodoItem ... items) {
TodoList mockTodoList = mock(TodoList.class);
......
......@@ -15,7 +15,8 @@ import java.util.*;
import java.util.stream.IntStream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
......@@ -87,7 +88,8 @@ class TodoListServiceTest {
TodoList savedList = todoListService.getTodoListById(1L);
assertFalse(savedList.getItems().isEmpty());
assertFalse(savedList.getItems()
.isEmpty());
}
@Test
......@@ -105,9 +107,11 @@ class TodoListServiceTest {
when(todoListRepository.save(any(TodoList.class))).thenReturn(new TodoList(List.of(todoItem)));
TodoList savedList = todoListService.addTodoItem(todoItem);
TodoItem savedTodoItem = savedList.getItems().get(0);
TodoItem savedTodoItem = savedList.getItems()
.get(0);
assertFalse(savedList.getItems().isEmpty());
assertFalse(savedList.getItems()
.isEmpty());
assertEquals("Buy milk", savedTodoItem.getTitle());
}
......@@ -119,7 +123,9 @@ class TodoListServiceTest {
todoListService.addTodoItem(1L, new TodoItem("Touch grass"));
assertEquals(2, list.getItems().size(), "The numbers of items in the list: " + list.getItems().size());
assertEquals(2, list.getItems()
.size(), "The numbers of items in the list: " + list.getItems()
.size());
}
@Test
......@@ -140,7 +146,9 @@ class TodoListServiceTest {
todoListService.updateTodoItem(1L, 1L, true);
assertTrue(list.getItems().stream().anyMatch(TodoItem::getFinished));
assertTrue(list.getItems()
.stream()
.anyMatch(TodoItem::getFinished));
}
@Test
......@@ -178,7 +186,8 @@ class TodoListServiceTest {
@DisplayName("Given a list with few items all finished, computeMotivationMessage should produce the correct message")
void computeMotivationMessage_fewItems_allFinished() {
TodoList list = createTodoList("Get outside", "Touch grass", "Breathe air", "Buy milk");
list.getItems().forEach(item -> item.setFinished(true));
list.getItems()
.forEach(item -> item.setFinished(true));
String message = todoListService.computeMotivationMessage(list);
......@@ -192,10 +201,12 @@ class TodoListServiceTest {
@DisplayName("Given a list with few items half finished, computeMotivationMessage should produce the correct message")
void computeMotivationMessage_fewItems_halfFinished() {
TodoList list = createTodoList("Get outside", "Touch grass", "Breathe air", "Buy milk");
int itemCount = list.getItems().size();
int itemCount = list.getItems()
.size();
for (int i = 0; i < itemCount / 2; i++) {
TodoItem item = list.getItems().get(i);
TodoItem item = list.getItems()
.get(i);
item.setFinished(true);
}
......@@ -211,7 +222,9 @@ class TodoListServiceTest {
@DisplayName("Given a list with few items and single item finished, computeMotivationMessage should produce the correct message")
void computeMotivationMessage_fewItems_singleFinished() {
TodoList list = createTodoList("Get outside", "Touch grass", "Breathe air", "Buy milk");
list.getItems().get(0).setFinished(true);
list.getItems()
.get(0)
.setFinished(true);
String message = todoListService.computeMotivationMessage(list);
......@@ -246,7 +259,8 @@ class TodoListServiceTest {
.mapToObj(i -> "Task " + i)
.toArray(String[]::new);
TodoList list = createTodoList(items);
list.getItems().forEach(todoItem -> todoItem.setFinished(true));
list.getItems()
.forEach(todoItem -> todoItem.setFinished(true));
String message = todoListService.computeMotivationMessage(list);
......@@ -264,10 +278,12 @@ class TodoListServiceTest {
.mapToObj(i -> "Task " + i)
.toArray(String[]::new);
TodoList list = createTodoList(items);
int countItems = list.getItems().size();
int countItems = list.getItems()
.size();
for (int i = 0; i < countItems / 2; i++) {
TodoItem item = list.getItems().get(i);
TodoItem item = list.getItems()
.get(i);
item.setFinished(true);
}
......@@ -287,7 +303,9 @@ class TodoListServiceTest {
.mapToObj(i -> "Task " + i)
.toArray(String[]::new);
TodoList list = createTodoList(items);
list.getItems().get(0).setFinished(true);
list.getItems()
.get(0)
.setFinished(true);
String message = todoListService.computeMotivationMessage(list);
......@@ -301,8 +319,8 @@ class TodoListServiceTest {
TodoList list = new TodoList(new ArrayList<>());
Arrays.stream(items)
.map(TodoItem::new)
.forEach(list::addTodoItem);
.map(TodoItem::new)
.forEach(list::addTodoItem);
return list;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment