diff --git a/.github/workflows/unit-tests-push.yml b/.github/workflows/unit-tests-push.yml
new file mode 100644
index 00000000..11deb24d
--- /dev/null
+++ b/.github/workflows/unit-tests-push.yml
@@ -0,0 +1,61 @@
+name: Application tests
+
+on:
+ push:
+ branches:
+ - master
+ - unit-tests
+ - develop
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+jobs:
+ app-tests-analyze:
+ runs-on: ubuntu-latest
+ services:
+ mysql:
+ image: mysql:latest
+ env:
+ MYSQL_ROOT_PASSWORD: root
+ MYSQL_DATABASE: test_database
+ ports:
+ - 3306:3306
+ options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Cache Maven packages
+ uses: actions/cache@v2
+ with:
+ path: |
+ ~/.m2
+ key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-m2
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v1
+ with:
+ java-version: '17'
+
+ - name: Add exec permission to mvnw
+ run: chmod +x mvnw
+
+ - name: Compile the application
+ run: ./mvnw -B clean install -DskipTests=true
+
+ - name: Start the application
+ run: ./mvnw spring-boot:run &
+ env:
+ SPRING_DATASOURCE_URL: jdbc:mysql://localhost:3306/test_database
+ SPRING_DATASOURCE_USERNAME: root
+ SPRING_DATASOURCE_PASSWORD: root
+ SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.cj.jdbc.Driver
+
+ - name: Run all tests with sonar analysis
+ run: |
+ ./mvnw -B verify sonar:sonar -Dsonar.projectKey=Arquisoft_wiq_es04b -Dsonar.organization=arquisoft -Dsonar.branch.name=${{ github.ref }} -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=${{ secrets.SONAR_TOKEN }} -Dspring.profiles.active=test -Dspring.datasource.url=jdbc:mysql://localhost:3306/test_database -Dspring.datasource.username=root -Dspring.datasource.password=root -Dspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+ env:
+ SPRING_PROFILES_ACTIVE: test
+ headless: true
\ No newline at end of file
diff --git a/README.md b/README.md
index 061b7c03..fd46ddc7 100644
--- a/README.md
+++ b/README.md
@@ -4,128 +4,6 @@
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es04b&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es04b)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es04b&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es04b)
-This is a base repo for the [Software Architecture course](http://arquisoft.github.io/)
-in [2023/2024 edition](https://arquisoft.github.io/course2324.html).
-
-This repo is a basic application composed of several components.
-
-- **Gateway service**. Express service that is exposed to the public and serves as a proxy to the two previous ones.
-- **User service**. Express service that handles the insertion of new users in the system.
-- **Auth service**. Express service that handles the authentication of users.
-- **Webapp**. React web application that uses the gateway service to allow basic login and new user features.
-
-Both the user and auth service share a Mongo database that is accessed with mongoose.
-
-## Quick start guide
-
-### Using docker
-
-The fastest way for launching this sample project is using docker. Just clone the project:
-
-```sh
-git clone https://github.com/Arquisoft/wiq_es04b.git
-```
-
-and launch it with docker compose:
-
-```sh
-docker compose --profile dev up --build
-```
-
-### Starting Component by component
-
-First, start the database. Either install and run Mongo or run it using docker:
-
-```docker run -d -p 27017:27017 --name=my-mongo mongo:latest```
-
-You can also use services like Mongo Altas for running a Mongo database in the cloud.
-
-Now, launch the auth, user and gateway services. Just go to each directory and run `npm install` followed
-by `npm start`.
-
-Lastly, go to the webapp directory and launch this component with `npm install` followed by `npm start`.
-
-After all the components are launched, the app should be available in localhost in port 3000.
-
-## Deployment
-
-For the deployment, we have several options.
-
-The first and more flexible is to deploy to a virtual machine using SSH. This will work with any cloud service (or with
-our own server).
-
-Other options include using the container services that most cloud services provide. This means, deploying our Docker
-containers directly.
-
-We are going to use the first approach, creating a virtual machine in a cloud service and after installing docker and
-docker-compose, deploy our containers there using GitHub Actions and SSH.
-
-### Machine requirements for deployment
-
-The machine for deployment can be created in services like Microsoft Azure or Amazon AWS. These are in general the
-settings that it must have:
-
-- Linux machine with Ubuntu > 20.04.
-- Docker and docker-compose installed.
-- Open ports for the applications installed (in this case, ports 3000 for the webapp and 8000 for the gateway service).
-
-Once you have the virtual machine created, you can install **docker** and **docker-compose** using the following
-instructions:
-
-```ssh
-sudo apt update
-sudo apt install apt-transport-https ca-certificates curl software-properties-common
-curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
-sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
-sudo apt update
-sudo apt install docker-ce
-sudo usermod -aG docker ${USER}
-sudo curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
-sudo chmod +x /usr/local/bin/docker-compose
-```
-
-### Continuous delivery (GitHub Actions)
-
-Once we have our machine ready, we could deploy by hand the application, taking our docker-compose file and executing it
-in the remote machine.
-
-In this repository, this process is done automatically using **GitHub Actions**. The idea is to trigger a series of
-actions when some condition is met in the repository.
-
-As you can see, unitary tests of each module and e2e tests are executed before pushing the docker images and deploying
-them. Using this approach we avoid deploying versions that do not pass the tests.
-
-The deploy action is the following:
-
-```yml
-deploy:
- name: Deploy over SSH
- runs-on: ubuntu-latest
- needs: [docker-push-userservice,docker-push-authservice,docker-push-gatewayservice,docker-push-webapp]
- steps:
- - name: Deploy over SSH
- uses: fifsky/ssh-action@master
- with:
- host: ${{ secrets.DEPLOY_HOST }}
- user: ${{ secrets.DEPLOY_USER }}
- key: ${{ secrets.DEPLOY_KEY }}
- command: |
- wget https://raw.githubusercontent.com/arquisoft/wiq_es04b/master/docker-compose-deploy.yml -O docker-compose.yml
- wget https://raw.githubusercontent.com/arquisoft/wiq_es04b/master/.env -O .env
- docker compose down
- docker compose --profile prod up -d
-```
-
-This action uses three secrets that must be configured in the repository:
-
-- DEPLOY_HOST: IP of the remote machine.
-- DEPLOY_USER: user with permission to execute the commands in the remote machine.
-- DEPLOY_KEY: key to authenticate the user in the remote machine.
-
-Note that this action logs in the remote machine and downloads the docker-compose file from the repository and launches
-it. Obviously, previous actions have been executed which have uploaded the docker images to the GitHub Packages
-repository.
-
### 🚀 TEAM
- **Daniel Alvarez Blanco**
diff --git a/pom.xml b/pom.xml
index 86487ac8..e604b79b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,7 +72,22 @@
commons-validator
1.7
-
+
+ org.seleniumhq.selenium
+ selenium-java
+ 4.1.0
+
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ 5.0.3
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ 5.1
+
diff --git a/src/main/java/com/uniovi/services/InsertSampleDataService.java b/src/main/java/com/uniovi/services/InsertSampleDataService.java
index 71253e4e..9248c2fb 100644
--- a/src/main/java/com/uniovi/services/InsertSampleDataService.java
+++ b/src/main/java/com/uniovi/services/InsertSampleDataService.java
@@ -19,6 +19,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
+import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@@ -31,22 +32,29 @@ public class InsertSampleDataService {
private final CategoryService categoryService;
private final QuestionRepository questionRepository;
private final GameSessionRepository gameSessionRepository;
+ private Environment environment;
private Logger log = LoggerFactory.getLogger(InsertSampleDataService.class);;
public InsertSampleDataService(PlayerService playerService, QuestionService questionService,
CategoryService categoryService, QuestionRepository questionRepository,
- GameSessionRepository gameSessionRepository) {
+ GameSessionRepository gameSessionRepository, Environment environment) {
this.playerService = playerService;
this.questionService = questionService;
this.categoryService = categoryService;
this.questionRepository = questionRepository;
this.gameSessionRepository = gameSessionRepository;
+ this.environment = environment;
}
@Transactional
@EventListener(ApplicationReadyEvent.class) // Uncomment this line to insert sample data on startup
public void insertSampleQuestions() {
+ if (Arrays.stream(environment.getActiveProfiles()).anyMatch(env -> (env.equalsIgnoreCase("test")))) {
+ log.info("Test profile active, skipping sample data insertion");
+ return;
+ }
+
if (!playerService.getUserByEmail("test@test.com").isPresent()) {
PlayerDto player = new PlayerDto();
player.setEmail("test@test.com");
diff --git a/src/test/java/com/uniovi/WiqEs04bApplicationTests.java b/src/test/java/com/uniovi/WiqEs04bApplicationTests.java
deleted file mode 100644
index 35eacaa0..00000000
--- a/src/test/java/com/uniovi/WiqEs04bApplicationTests.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.uniovi;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class WiqEs04bApplicationTests {
-
- @Test
- void contextLoads() {
- }
-
-}
diff --git a/src/test/java/com/uniovi/Wiq_IntegrationTests.java b/src/test/java/com/uniovi/Wiq_IntegrationTests.java
new file mode 100644
index 00000000..074e43e3
--- /dev/null
+++ b/src/test/java/com/uniovi/Wiq_IntegrationTests.java
@@ -0,0 +1,61 @@
+package com.uniovi;
+
+import io.github.bonigarcia.wdm.WebDriverManager;
+import org.junit.jupiter.api.*;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.firefox.FirefoxDriver;
+import org.openqa.selenium.firefox.FirefoxOptions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.env.Environment;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+@SpringBootTest
+@Tag("integration")
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@ActiveProfiles("test")
+class Wiq_IntegrationTests {
+ static final String URL = "http://localhost:3000/";
+
+ static WebDriver driver;
+
+ @Autowired
+ Environment env;
+
+ @BeforeEach
+ public void begin() {
+ if (driver == null) {
+ WebDriverManager.firefoxdriver().setup();
+ if (env.getProperty("headless") != null && env.getProperty("headless").equals("true")) {
+ FirefoxOptions options = new FirefoxOptions();
+ options.addArguments("--headless");
+ driver = new FirefoxDriver(options);
+ } else {
+ driver = new FirefoxDriver();
+ }
+ }
+ driver.navigate().to(URL);
+ }
+
+ @AfterEach
+ void tearDown() {
+ driver.manage().deleteAllCookies();
+ }
+
+ @AfterAll
+ public static void end() {
+ driver.quit();
+ }
+
+ @Test
+ @Order(1)
+ void testHome() {
+ // Check the title
+ Assertions.assertEquals("Wikigame", driver.getTitle());
+ }
+}
diff --git a/src/test/java/com/uniovi/Wiq_UnitTests.java b/src/test/java/com/uniovi/Wiq_UnitTests.java
new file mode 100644
index 00000000..69e128cc
--- /dev/null
+++ b/src/test/java/com/uniovi/Wiq_UnitTests.java
@@ -0,0 +1,22 @@
+package com.uniovi;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+
+@SpringBootTest
+@Tag("unit")
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@ActiveProfiles("test")
+class Wiq_UnitTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/src/test/java/com/uniovi/util/SeleniumUtils.java b/src/test/java/com/uniovi/util/SeleniumUtils.java
new file mode 100644
index 00000000..21723d3b
--- /dev/null
+++ b/src/test/java/com/uniovi/util/SeleniumUtils.java
@@ -0,0 +1,119 @@
+package com.uniovi.util;
+
+
+import org.junit.jupiter.api.Assertions;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.time.Duration;
+import java.util.List;
+
+public class SeleniumUtils {
+ /**
+ * Aborta si el "texto" no está presente en la página actual
+ * @param driver: apuntando al navegador abierto actualmente.
+ * @param text: texto a buscar
+ */
+ static public void textIsPresentOnPage(WebDriver driver, String text)
+ {
+ List list = driver.findElements(By.xpath("//*[contains(text(),'" + text + "')]"));
+ Assertions.assertTrue(list.size() > 0, "Texto " + text + " no localizado!");
+ }
+
+ /**
+ * Aborta si el "texto" está presente en la página actual
+ * @param driver: apuntando al navegador abierto actualmente.
+ * @param text: texto a buscar
+ */
+ static public void textIsNotPresentOnPage(WebDriver driver, String text)
+ {
+ List list = driver.findElements(By.xpath("//*[contains(text(),'" + text + "')]"));
+ Assertions.assertEquals(0, list.size(), "Texto " + text + " no está presente !");
+ }
+
+ /**
+ * Aborta si el "texto" está presente en la página actual tras timeout segundos.
+ * @param driver: apuntando al navegador abierto actualmente.
+ * @param text: texto a buscar
+ * @param timeout: el tiempo máximo que se esperará por la aparición del texto a buscar
+ */
+ static public void waitTextIsNotPresentOnPage(WebDriver driver, String text, int timeout)
+ {
+ Boolean resultado =
+ (new WebDriverWait(driver, Duration.ofSeconds(timeout))).until(ExpectedConditions.invisibilityOfElementLocated(By.xpath("//*[contains(text(),'" + text + "')]")));
+
+ Assertions.assertTrue(resultado);
+ }
+
+
+ /**
+ * Espera por la visibilidad de un elemento/s en la vista actualmente cargandose en driver. Para ello se empleará una consulta xpath.
+ * @param driver: apuntando al navegador abierto actualmente.
+ * @param xpath: consulta xpath.
+ * @param timeout: el tiempo máximo que se esperará por la aparición del elemento a buscar con xpath
+ * @return Se retornará la lista de elementos resultantes de la búsqueda con xpath.
+ */
+ static public List waitLoadElementsByXpath(WebDriver driver, String xpath, int timeout)
+ {
+ WebElement result =
+ (new WebDriverWait(driver, Duration.ofSeconds(timeout))).until(ExpectedConditions.visibilityOfElementLocated(By.xpath(xpath)));
+ Assertions.assertNotNull(result);
+ return driver.findElements(By.xpath(xpath));
+ }
+
+ /**
+ * Espera por la visibilidad de un elemento/s en la vista actualmente cargandose en driver. Para ello se empleará una consulta xpath
+ * según varios criterios..
+ *
+ * @param driver: apuntando al navegador abierto actualmente.
+ * @param criterio: "id" or "class" or "text" or "@attribute" or "free". Si el valor de criterio es free es una expresion xpath completa.
+ * @param text: texto correspondiente al criterio.
+ * @param timeout: el tiempo máximo que se esperará por la apareción del elemento a buscar con criterio/text.
+ * @return Se retornará la lista de elementos resultantes de la búsqueda.
+ */
+ static public List waitLoadElementsBy(WebDriver driver, String criterio, String text, int timeout)
+ {
+ String searchCriterio;
+ switch (criterio) {
+ case "id":
+ searchCriterio = "//*[contains(@id,'" + text + "')]";
+ break;
+ case "class":
+ searchCriterio = "//*[contains(@class,'" + text + "')]";
+ break;
+ case "text":
+ searchCriterio = "//*[contains(text(),'" + text + "')]";
+ break;
+ case "free":
+ searchCriterio = text;
+ break;
+ default:
+ searchCriterio = "//*[contains(" + criterio + ",'" + text + "')]";
+ break;
+ }
+
+ return waitLoadElementsByXpath(driver, searchCriterio, timeout);
+ }
+
+
+ /**
+ * PROHIBIDO USARLO PARA VERSIÓN FINAL.
+ * Esperar "segundos" durante la ejecucion del navegador
+ * @param driver: apuntando al navegador abierto actualmente.
+ * @param seconds: Segundos de bloqueo de la ejecución en el navegador.
+ */
+ static public void waitSeconds(WebDriver driver, int seconds){
+
+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
+ synchronized(driver){
+ try {
+ driver.wait(seconds * 1000L);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}