diff --git a/acceptance-tests/README.md b/acceptance-tests/README.md
new file mode 100644
index 0000000..b92c191
--- /dev/null
+++ b/acceptance-tests/README.md
@@ -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
diff --git a/acceptance-tests/pom.xml b/acceptance-tests/pom.xml
new file mode 100644
index 0000000..f3ffe0f
--- /dev/null
+++ b/acceptance-tests/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+ com.inifi
+ unifi-network-tests
+ 1.0.0
+
+
+ org.seleniumhq.selenium
+ selenium-chrome-driver
+ 4.24.0
+
+
+
+ org.seleniumhq.selenium
+ selenium-java
+ 4.24.0
+
+
+
+ io.rest-assured
+ rest-assured
+ 5.5.0
+ test
+
+
+
+
+ org.testng
+ testng
+ 7.10.2
+ test
+
+
+ com.github.docker-java
+ docker-java
+ 3.2.12
+
+
+
+ 17
+ 17
+
+
+
+
+ acceptance-tests
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+
+
+
+
diff --git a/acceptance-tests/src/main/java/com/unifi/driver/BrowserManager.java b/acceptance-tests/src/main/java/com/unifi/driver/BrowserManager.java
new file mode 100644
index 0000000..7bc6f5c
--- /dev/null
+++ b/acceptance-tests/src/main/java/com/unifi/driver/BrowserManager.java
@@ -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;
+ }
+}
diff --git a/acceptance-tests/src/main/java/com/unifi/driver/DriverProvider.java b/acceptance-tests/src/main/java/com/unifi/driver/DriverProvider.java
new file mode 100644
index 0000000..8a51c15
--- /dev/null
+++ b/acceptance-tests/src/main/java/com/unifi/driver/DriverProvider.java
@@ -0,0 +1,30 @@
+package com.unifi.driver;
+
+import org.openqa.selenium.WebDriver;
+
+public class DriverProvider {
+
+ private static ThreadLocal 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);
+ }
+}
diff --git a/acceptance-tests/src/main/java/com/unifi/driver/pages/BasePage.java b/acceptance-tests/src/main/java/com/unifi/driver/pages/BasePage.java
new file mode 100644
index 0000000..9175a3e
--- /dev/null
+++ b/acceptance-tests/src/main/java/com/unifi/driver/pages/BasePage.java
@@ -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();
+ }
+}
diff --git a/acceptance-tests/src/main/java/com/unifi/driver/pages/DashboardPage.java b/acceptance-tests/src/main/java/com/unifi/driver/pages/DashboardPage.java
new file mode 100644
index 0000000..57bb3db
--- /dev/null
+++ b/acceptance-tests/src/main/java/com/unifi/driver/pages/DashboardPage.java
@@ -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);
+ }
+}
diff --git a/acceptance-tests/src/main/java/com/unifi/driver/pages/InitSetupPage.java b/acceptance-tests/src/main/java/com/unifi/driver/pages/InitSetupPage.java
new file mode 100644
index 0000000..b7e5837
--- /dev/null
+++ b/acceptance-tests/src/main/java/com/unifi/driver/pages/InitSetupPage.java
@@ -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);
+ }
+}
diff --git a/acceptance-tests/src/main/java/com/unifi/driver/pages/RegisterPage.java b/acceptance-tests/src/main/java/com/unifi/driver/pages/RegisterPage.java
new file mode 100644
index 0000000..22a194f
--- /dev/null
+++ b/acceptance-tests/src/main/java/com/unifi/driver/pages/RegisterPage.java
@@ -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);
+ }
+}
diff --git a/acceptance-tests/src/main/java/com/unifi/driver/utils/DockerContainerManager.java b/acceptance-tests/src/main/java/com/unifi/driver/utils/DockerContainerManager.java
new file mode 100644
index 0000000..393e38e
--- /dev/null
+++ b/acceptance-tests/src/main/java/com/unifi/driver/utils/DockerContainerManager.java
@@ -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);
+ }
+
+}
diff --git a/acceptance-tests/src/test/java/com/unifi/tests/API/BaseAPITest.java b/acceptance-tests/src/test/java/com/unifi/tests/API/BaseAPITest.java
new file mode 100644
index 0000000..60ddf0c
--- /dev/null
+++ b/acceptance-tests/src/test/java/com/unifi/tests/API/BaseAPITest.java
@@ -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");
+ }
+}
diff --git a/acceptance-tests/src/test/java/com/unifi/tests/API/CheckAdminAttributesAPITest.java b/acceptance-tests/src/test/java/com/unifi/tests/API/CheckAdminAttributesAPITest.java
new file mode 100644
index 0000000..7730c2b
--- /dev/null
+++ b/acceptance-tests/src/test/java/com/unifi/tests/API/CheckAdminAttributesAPITest.java
@@ -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);
+ }
+}
diff --git a/acceptance-tests/src/test/java/com/unifi/tests/BaseTest.java b/acceptance-tests/src/test/java/com/unifi/tests/BaseTest.java
new file mode 100644
index 0000000..a49c619
--- /dev/null
+++ b/acceptance-tests/src/test/java/com/unifi/tests/BaseTest.java
@@ -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();
+ }
+
+
+
+
+}
diff --git a/acceptance-tests/src/test/java/com/unifi/tests/CheckAdminAttributesTest.java b/acceptance-tests/src/test/java/com/unifi/tests/CheckAdminAttributesTest.java
new file mode 100644
index 0000000..0313a03
--- /dev/null
+++ b/acceptance-tests/src/test/java/com/unifi/tests/CheckAdminAttributesTest.java
@@ -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));
+
+ }
+}
diff --git a/db/init-mongo.sh b/db/init-mongo.sh
new file mode 100644
index 0000000..ec6fca0
--- /dev/null
+++ b/db/init-mongo.sh
@@ -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}" <
+
+ 4.0.0
+
+ com.inifi
+ unifi-network-application
+ 1.0.0
+ pom
+
+ unifi-network-application-parent
+
+
+ acceptance-tests
+
+
+