added acceptance tests

This commit is contained in:
SG0316106 2024-09-18 10:23:52 +02:00
parent 2493d03013
commit b6f6952935
16 changed files with 641 additions and 0 deletions

View file

@ -0,0 +1,6 @@
Use following steps to run acceptance test locally:
1. Install Docker
2. mvn clean install -Dmaven.test.skip=true
3. Run `docker-compose.yaml` // DO NOT forget to provide absolute path to init-mongo.sh script
4. mvn clean install or run tests manually

61
acceptance-tests/pom.xml Normal file
View file

@ -0,0 +1,61 @@
<?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>
<groupId>com.inifi</groupId>
<artifactId>unifi-network-tests</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
<version>4.24.0</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.24.0</version>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>3.2.12</version> <!-- Use the latest stable version -->
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<profiles>
<profile>
<id>acceptance-tests</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -0,0 +1,28 @@
package com.unifi.driver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.time.Duration;
public class BrowserManager {
ChromeOptions options;
WebDriver driver;
public WebDriver create() {
options = new ChromeOptions();
options.addArguments("--ignore-certificate-errors", "--disable-search-engine-choice-screen");
driver = new ChromeDriver(options);
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(Duration.ZERO);
return driver;
}
public WebDriver getDriver() {
if (driver == null) {
return create();
}
return driver;
}
}

View file

@ -0,0 +1,30 @@
package com.unifi.driver;
import org.openqa.selenium.WebDriver;
public class DriverProvider {
private static ThreadLocal<WebDriver> drivers = new ThreadLocal<>();
private DriverProvider() {
}
public static WebDriver getDriverInstance() {
if (drivers.get() == null) {
createNewDriver();
}
return drivers.get();
}
public static void closeDriver() {
if (drivers.get() != null) {
drivers.get().quit();
drivers.remove();
}
}
private static void createNewDriver() {
WebDriver driver = new BrowserManager().create();
drivers.set(driver);
}
}

View file

@ -0,0 +1,37 @@
package com.unifi.driver.pages;
import com.unifi.driver.DriverProvider;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class BasePage {
WebDriver driver = DriverProvider.getDriverInstance();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofMillis(30000));
public void clickElement(By by) {
System.out.println("Clicking on element " + by.toString());
wait.until(ExpectedConditions.elementToBeClickable(by)).click();
}
public void focusAndClickElement(By by) {
System.out.println("Focusing the element " + by.toString());
new Actions(driver).moveToElement(wait.until(ExpectedConditions.elementToBeClickable(by))).click().perform();
}
public void sendKeysToElement(String text, By by) {
System.out.println("Sending keys onto element " + by.toString());
wait.until(ExpectedConditions.elementToBeClickable(by)).sendKeys(text);
}
public String getTextFromElement(By by) {
return driver.findElement(by).getText();
}
}

View file

@ -0,0 +1,30 @@
package com.unifi.driver.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
public class DashboardPage extends BasePage{
private static By dashboardButton = By.xpath("//a[@class='component__rHEvxowz icon-light__rHEvxowz active__rHEvxowz']");
private static By topologyButton = By.xpath("//a[@class='component__rHEvxowz icon-light__rHEvxowz active__rHEvxowz']");
private static By textFromActibvityTab = By.xpath("/html[1]/body[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[4]/div[1]/span[1]");
private static By settingsTab = By.xpath("//a[@data-testid='navigation-settings']");
private static By systemTab = By.xpath("//span[@data-testid='system']");
private static By countryField = By.xpath("//div[@class='inputContainer__xvBRiNo6 inputContainer-light-secondary__xvBRiNo6 inputContainer-secondary__xvBRiNo6']");
public void makeSomeActivitiesForAdminChecking(){
clickElement(topologyButton);
clickElement(dashboardButton);
driver.navigate().refresh();
wait.until(ExpectedConditions.visibilityOfElementLocated(textFromActibvityTab));
}
public static By getTextFromActibvityTab() {
return textFromActibvityTab;
}
public String checkCountryCode() {
clickElement(settingsTab);
clickElement(systemTab);
return getTextFromElement(countryField);
}
}

View file

@ -0,0 +1,41 @@
package com.unifi.driver.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
public class InitSetupPage extends BasePage{
private static By termsCheckBox = By.xpath("//input[@id='tosAndEula']");
private static By nextButton = By.cssSelector("button[type='submit'] span[class='content__jTNy2Cxe']");
private static By skipButton = By.xpath("//span[contains(text(),'Skip')]");
private static By createUIAccountButton = By.cssSelector("button[class='button__jTNy2Cxe button-light__jTNy2Cxe secondary__jTNy2Cxe secondary-light__jTNy2Cxe is-accessible__jTNy2Cxe is-accessible-light__jTNy2Cxe medium__jTNy2Cxe'] span[class='content__jTNy2Cxe'] span");
private static By advancedSetupLink = By.xpath("//span[contains(text(),'Advanced Setup')]");
private static String countryCodeText;
private static By countryCode = By.xpath("//*[@id=\"root\"]/div[1]/div[3]/div/form/div/div[2]/div[2]");
public static String getCountryCodeText() {
return countryCodeText;
}
public static By getTermsCheckBox() {
return termsCheckBox;
}
public static By getNextButton() {
return nextButton;
}
public static By getCreateUIAccountButton() {
return createUIAccountButton;
}
public void prepareForSetupAccount(){
countryCodeText = wait.until(ExpectedConditions.presenceOfElementLocated(countryCode)).getText();
clickElement(termsCheckBox);
clickElement(nextButton);
clickElement(advancedSetupLink);
clickElement(skipButton);
}
}

View file

@ -0,0 +1,32 @@
package com.unifi.driver.pages;
import org.openqa.selenium.By;
public class RegisterPage extends BasePage{
private String userName;
private String email;
private String password;
private static By userNameField = By.xpath("//input[@id='localAdminUsername']");
private static By userEmailField = By.xpath("//input[@id='localAdminEmail']");
private static By userPasswordField = By.xpath("//input[@id='localAdminPassword']");
private static By userConfirmPasswordField = By.xpath("//input[@id='localAdminPasswordConfirm']");
private static By finishButton = By.xpath("//span[normalize-space()='Finish']");
public RegisterPage(String userName, String email, String password) {
this.userName = userName;
this.email = email;
this.password = password;
}
public void fillFormsAndFinish () {
sendKeysToElement(this.userName, userNameField);
sendKeysToElement(this.email, userEmailField);
sendKeysToElement(this.password, userPasswordField);
sendKeysToElement(this.password, userConfirmPasswordField);
clickElement(finishButton);
}
}

View file

@ -0,0 +1,91 @@
package com.unifi.driver.utils;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.StartContainerCmd;
import com.github.dockerjava.api.command.StopContainerCmd;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class DockerContainerManager {
private DockerClient dockerClient;
public String getContainerName() {
return containerName;
}
private String containerName = "unifi-network-application";
public DockerContainerManager() {
DockerClientConfig dockerClientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder()
.withDockerHost("tcp://localhost:2375")
.withDockerTlsVerify(false)
.build();
this.dockerClient = DockerClientBuilder.getInstance(dockerClientConfig).build();
}
public void restartContainer(String containerId) {
try {
StopContainerCmd stopCmd = dockerClient.stopContainerCmd(containerId);
stopCmd.exec();
System.out.println("Container " + containerId + " stopped.");
StartContainerCmd startCmd = dockerClient.startContainerCmd(containerId);
startCmd.exec();
System.out.println("Container " + containerId + " started.");
} catch (Exception e) {
e.printStackTrace();
}
}
private String runCommand(String command) {
StringBuilder output = new StringBuilder();
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("bash", "-c", command);
try {
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Error executing command: " + command);
}
} catch (IOException | InterruptedException e) {
System.err.println("Error: " + e.getMessage());
}
return output.toString().trim();
}
public void editFile(String filePath, String searchPattern, String replacement) {
String escapedSearchPattern = searchPattern.replace("/", "\\/");
String escapedReplacement = replacement.replace("/", "\\/");
String command = String.format(
"docker exec %s sed -i 's/%s/%s/g' %s",
containerName, escapedSearchPattern, escapedReplacement, filePath
);
runCommand(command);
}
public void editSystemProperties(){
String filePath = "/usr/lib/unifi/data/system.properties";
String searchPattern = "is_default=false";
String replacement = "is_default=true";
this.editFile(filePath, searchPattern, replacement);
}
}

View file

@ -0,0 +1,92 @@
package com.unifi.tests.API;
import com.unifi.driver.utils.DockerContainerManager;
import io.restassured.RestAssured;
import io.restassured.http.Cookies;
import io.restassured.response.Response;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import static io.restassured.RestAssured.given;
public class BaseAPITest {
private String username = "admin";
private String password = "password";
private DockerContainerManager dockerContainerManager = new DockerContainerManager();
private Cookies cookies;
public void initRestAssured () {
RestAssured.baseURI = "https://127.0.0.1";
RestAssured.port = 8443;
RestAssured.basePath = "/api";
}
@BeforeClass
public void setup(){
initRestAssured();
dockerContainerManager.editSystemProperties();
dockerContainerManager.restartContainer(dockerContainerManager.getContainerName());
//wait until application starting ...
//TODO: check docker logs instead of sleeping
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
createNewUser();
}
public Response makeGetRequestAndReturnResponse (String path) {
return given()
.relaxedHTTPSValidation()
.cookie(String.valueOf(cookies))
.header("Content-Type","application/json").header("connection","keep-alive").header("Accept-Encoding","gzp,deflate,br")
.when()
.get(path)
.then()
.extract().response();
}
public void initLogin(String username, String password) {
String initAdminLoginPayload = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\",\"remember\":false,\"strict\":true}";
Response response = given()
.relaxedHTTPSValidation()
.body(initAdminLoginPayload)
.when()
.post("/login")
.then()
.extract().response();
cookies = response.getDetailedCookies();
Assert.assertEquals(response.statusCode(), 200);
}
public void makePostRequest(String body, String path){
Response response = given()
.cookie(String.valueOf(cookies))
.relaxedHTTPSValidation()
.body(body)
.when()
.post(path)
.then()
.extract().response();
Assert.assertEquals(response.statusCode(), 200);
}
public void createNewUser(){
String newUserBody = "{\"cmd\": \"add-default-admin\", \"name\": \"admin\", \"email\": \"network-admin@gmail.com\", \"x_password\": \"password\"}";
makePostRequest(newUserBody,"/cmd/sitemgr");
String applicationName = "{\"name\": \"UniFi Network\"}";
makePostRequest(applicationName, "/set/setting/super_identity");
String countryCode = "{\"code\": \"840\"}";
makePostRequest(countryCode, "/set/setting/country");
String timeZone = "{\"timezone\": \"Europe/Riga\"}";
makePostRequest(timeZone, "/set/setting/locale");
String configuredState = "{\"cmd\": \"set-installed\"}";
makePostRequest(configuredState, "/cmd/system");
}
}

View file

@ -0,0 +1,33 @@
package com.unifi.tests.API;
import io.restassured.response.Response;
import org.testng.Assert;
import org.testng.annotations.Test;
public class CheckAdminAttributesAPITest extends BaseAPITest {
Response response;
private String username = "admin";
private String password = "password";
@Test(priority = 2)
public void checkAdminName(){
System.out.println("Starting checkAdminName test");
initLogin(username, password);
response = makeGetRequestAndReturnResponse("/self");
String actualName = response.jsonPath().getString("data[0].name");
Assert.assertEquals(actualName, "admin");
}
@Test(priority = 3)
public void checkCountryCode() {
System.out.println("Starting checkCountryCode test");
initLogin(username, password);
response = makeGetRequestAndReturnResponse("/s/default/get/setting/country");
String expectedCountryCode = "840";
String actualCountryCode = response.jsonPath().getString("data[0].code");
Assert.assertEquals(actualCountryCode, expectedCountryCode);
}
}

View file

@ -0,0 +1,49 @@
package com.unifi.tests;
import com.unifi.driver.DriverProvider;
import com.unifi.driver.pages.InitSetupPage;
import com.unifi.driver.pages.RegisterPage;
import com.unifi.driver.utils.DockerContainerManager;
import org.openqa.selenium.WebDriver;
import org.testng.annotations.*;
public class BaseTest {
protected WebDriver driver;
protected InitSetupPage initSetupPage;
protected RegisterPage registerPage;
private String name = "admin";
private String email = "network-admin@gmail.com";
private String password = "password";
private String URL = "https://127.0.0.1:8443/";
private DockerContainerManager dockerContainerManager = new DockerContainerManager();
@BeforeClass
public void setup() {
dockerContainerManager.editSystemProperties();
dockerContainerManager.restartContainer(dockerContainerManager.getContainerName());
//wait until application starting ...
//TODO: check docker logs instead of sleeping
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.driver = DriverProvider.getDriverInstance();
driver.get(URL);
initSetupPage = new InitSetupPage();
initSetupPage.prepareForSetupAccount();
registerPage = new RegisterPage(name, email, password);
registerPage.fillFormsAndFinish();
}
@AfterClass
public void close(){
driver.quit();
}
}

View file

@ -0,0 +1,26 @@
package com.unifi.tests;
import com.unifi.driver.pages.DashboardPage;
import com.unifi.driver.pages.InitSetupPage;
import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;
public class CheckAdminAttributesTest extends BaseTest{
private DashboardPage dashboardPage;
String expectedAdminActivities = "admin opened UniFi Network via the web.";
private SoftAssert softAssert = new SoftAssert();
@Test(priority = 1)
public void isAdminNameIsPresentInActivityTabAndCountryCodeIsTheSame(){
System.out.println("Starting isAdminNameIsPresentInActivityTabAndCountryCodeIsTheSame test");
dashboardPage = new DashboardPage();
dashboardPage.makeSomeActivitiesForAdminChecking();
String actualActivityText = dashboardPage.getTextFromElement(DashboardPage.getTextFromActibvityTab());
softAssert.assertTrue(actualActivityText.equals(expectedAdminActivities));
String actualCountryCode = dashboardPage.checkCountryCode();
softAssert.assertTrue(InitSetupPage.getCountryCodeText().contains(actualCountryCode));
}
}

19
db/init-mongo.sh Normal file
View file

@ -0,0 +1,19 @@
#!/bin/bash
echo "Run script"
if which mongosh > /dev/null 2>&1; then
mongo_init_bin='mongosh'
else
mongo_init_bin='mongo'
fi
"${mongo_init_bin}" <<EOF
use ${MONGO_AUTHSOURCE}
db.auth("${MONGO_INITDB_ROOT_USERNAME}", "${MONGO_INITDB_ROOT_PASSWORD}")
db.createUser({
user: "${MONGO_USER}",
pwd: "${MONGO_PASS}",
roles: [
{ db: "${MONGO_DBNAME}", role: "dbOwner" },
{ db: "${MONGO_DBNAME}_stat", role: "dbOwner" }
]
})
EOF

49
docker-compose.yaml Normal file
View file

@ -0,0 +1,49 @@
version: "3.8"
services:
unifi-db:
image : mongo:7.0.5
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root
- MONGO_USER=unifi
- MONGO_PASS=unifi
- MONGO_DBNAME=unifi
- MONGO_AUTHSOURCE=admin
volumes:
# - ./docker-unifi-network-application/db/data:/data/db
- ./unifi/docker-unifi-network-application/db/init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh:ro #absolute path required
ports:
- 27017:27017
restart: unless-stopped
unifi-network-application:
image: lscr.io/linuxserver/unifi-network-application:latest
container_name: unifi-network-application
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- MONGO_USER=unifi
- MONGO_PASS=unifi
- MONGO_HOST=unifi-db
- MONGO_PORT=27017
- MONGO_DBNAME=unifi
- MONGO_AUTHSOURCE=admin
- MEM_LIMIT=1024
- MEM_STARTUP=1024
- MONGO_TLS=
volumes:
- /docker-unifi-network-application/config:/config
ports:
- 8443:8443
- 3478:3478/udp
- 10001:10001/udp
- 8080:8080
- 1900:1900/udp
- 8843:8843
- 8880:8880
- 6789:6789
- 5514:5514/udp
restart: unless-stopped
depends_on:
- unifi-db

17
pom.xml Normal file
View file

@ -0,0 +1,17 @@
<?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>
<groupId>com.inifi</groupId>
<artifactId>unifi-network-application</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>unifi-network-application-parent</name>
<modules>
<module>acceptance-tests</module>
</modules>
</project>