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 + + +