Skip to content

Commit

Permalink
Merge pull request #158 from Arquisoft/unit-tests
Browse files Browse the repository at this point in the history
Add test framework
  • Loading branch information
Pelayori authored Mar 19, 2024
2 parents 258da91 + 940dab1 commit 76e4d3d
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 137 deletions.
61 changes: 61 additions & 0 deletions .github/workflows/unit-tests-push.yml
Original file line number Diff line number Diff line change
@@ -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
122 changes: 0 additions & 122 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
17 changes: 16 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,22 @@
<artifactId>commons-validator</artifactId>
<version>1.7</version>
</dependency>

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.1.0</version> <!-- Use latest version -->
</dependency>
<!-- WebDriver Manager -->
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.0.3</version> <!-- Use latest version -->
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.1</version> <!-- Check for the latest version -->
</dependency>
</dependencies>

<build>
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/uniovi/services/InsertSampleDataService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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("[email protected]").isPresent()) {
PlayerDto player = new PlayerDto();
player.setEmail("[email protected]");
Expand Down
13 changes: 0 additions & 13 deletions src/test/java/com/uniovi/WiqEs04bApplicationTests.java

This file was deleted.

61 changes: 61 additions & 0 deletions src/test/java/com/uniovi/Wiq_IntegrationTests.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
22 changes: 22 additions & 0 deletions src/test/java/com/uniovi/Wiq_UnitTests.java
Original file line number Diff line number Diff line change
@@ -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() {
}

}
Loading

0 comments on commit 76e4d3d

Please sign in to comment.