From cc7f545528bb6b1003a3104611c37f4084caa79c Mon Sep 17 00:00:00 2001 From: icarusiftctts Date: Sat, 11 Oct 2025 03:59:27 +0530 Subject: [PATCH] Adding Mock Implementation for LambdaTest API --- build.gradle | 8 ++ .../github/lambdatest/gradle/Constants.java | 42 +++++- .../lambdatest/gradle/TestExecutor.java | 4 +- .../lambdatest/gradle/UploaderUtil.java | 2 +- .../integration/ExecutionIntegrationTest.java | 105 +++++++++++++++ .../gradle/integration/FullWorkflowTest.java | 126 ++++++++++++++++++ .../integration/MockLambdaTestServer.java | 59 ++++++++ .../integration/UploadIntegrationTest.java | 85 ++++++++++++ src/test/resources/test-app.apk | 1 + src/test/resources/test-suite.apk | 1 + 10 files changed, 427 insertions(+), 6 deletions(-) create mode 100644 src/test/java/io/github/lambdatest/gradle/integration/ExecutionIntegrationTest.java create mode 100644 src/test/java/io/github/lambdatest/gradle/integration/FullWorkflowTest.java create mode 100644 src/test/java/io/github/lambdatest/gradle/integration/MockLambdaTestServer.java create mode 100644 src/test/java/io/github/lambdatest/gradle/integration/UploadIntegrationTest.java create mode 100644 src/test/resources/test-app.apk create mode 100644 src/test/resources/test-suite.apk diff --git a/build.gradle b/build.gradle index 97afcfb..b096014 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,10 @@ dependencies { implementation 'com.google.code.gson:gson:2.11.0' implementation 'org.apache.logging.log4j:log4j-api:2.24.1' implementation 'org.apache.logging.log4j:log4j-core:2.24.1' + + testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.mockito:mockito-core:5.1.1' } gradlePlugin { @@ -41,6 +45,10 @@ spotless { } } +test { + useJUnitPlatform() +} + javadoc { options.tags = [ "implNote:a:Implementation Note:" ] } diff --git a/src/main/java/io/github/lambdatest/gradle/Constants.java b/src/main/java/io/github/lambdatest/gradle/Constants.java index f67f673..4666f09 100644 --- a/src/main/java/io/github/lambdatest/gradle/Constants.java +++ b/src/main/java/io/github/lambdatest/gradle/Constants.java @@ -11,9 +11,45 @@ private Constants() { "This is a utility class and cannot be instantiated"); } - public static final String API_URL = "https://manual-api.lambdatest.com/app/uploadFramework"; - public static final String BUILD_URL = + private static final String DEFAULT_API_URL = + "https://manual-api.lambdatest.com/app/uploadFramework"; + private static final String DEFAULT_BUILD_URL = "https://mobile-api.lambdatest.com/framework/v1/espresso/build"; - public static final String FLUTTER_BUILD_URL = + private static final String DEFAULT_FLUTTER_BUILD_URL = "https://mobile-api.lambdatest.com/framework/v1/flutter/build"; + + // For testing purposes - allows URL override + private static String testApiUrl = null; + private static String testBuildUrl = null; + private static String testFlutterBuildUrl = null; + + public static String getApiUrl() { + return testApiUrl != null ? testApiUrl : DEFAULT_API_URL; + } + + public static String getBuildUrl() { + return testBuildUrl != null ? testBuildUrl : DEFAULT_BUILD_URL; + } + + public static String getFlutterBuildUrl() { + return testFlutterBuildUrl != null ? testFlutterBuildUrl : DEFAULT_FLUTTER_BUILD_URL; + } + + // Public methods for testing + public static void setTestUrls(String apiUrl, String buildUrl, String flutterBuildUrl) { + testApiUrl = apiUrl; + testBuildUrl = buildUrl; + testFlutterBuildUrl = flutterBuildUrl; + } + + public static void resetUrls() { + testApiUrl = null; + testBuildUrl = null; + testFlutterBuildUrl = null; + } + + // Backward compatibility - deprecated + @Deprecated public static final String API_URL = DEFAULT_API_URL; + @Deprecated public static final String BUILD_URL = DEFAULT_BUILD_URL; + @Deprecated public static final String FLUTTER_BUILD_URL = DEFAULT_FLUTTER_BUILD_URL; } diff --git a/src/main/java/io/github/lambdatest/gradle/TestExecutor.java b/src/main/java/io/github/lambdatest/gradle/TestExecutor.java index e7ed5ee..6599a9b 100644 --- a/src/main/java/io/github/lambdatest/gradle/TestExecutor.java +++ b/src/main/java/io/github/lambdatest/gradle/TestExecutor.java @@ -76,8 +76,8 @@ public void executeTests(Map params) throws IOException { String url = (isFlutter == null || !isFlutter) - ? Constants.BUILD_URL - : Constants.FLUTTER_BUILD_URL; + ? Constants.getBuildUrl() + : Constants.getFlutterBuildUrl(); RequestBody body = RequestBody.create(gson.toJson(capabilities), mediaType); Request request = diff --git a/src/main/java/io/github/lambdatest/gradle/UploaderUtil.java b/src/main/java/io/github/lambdatest/gradle/UploaderUtil.java index d692d42..5ecac51 100644 --- a/src/main/java/io/github/lambdatest/gradle/UploaderUtil.java +++ b/src/main/java/io/github/lambdatest/gradle/UploaderUtil.java @@ -59,7 +59,7 @@ public static String uploadAndGetId(String username, String accessKey, String fi .build(); Request request = new Request.Builder() - .url(Constants.API_URL) + .url(Constants.getApiUrl()) .addHeader("Authorization", Credentials.basic(username, accessKey)) .post(body) .build(); diff --git a/src/test/java/io/github/lambdatest/gradle/integration/ExecutionIntegrationTest.java b/src/test/java/io/github/lambdatest/gradle/integration/ExecutionIntegrationTest.java new file mode 100644 index 0000000..59b676b --- /dev/null +++ b/src/test/java/io/github/lambdatest/gradle/integration/ExecutionIntegrationTest.java @@ -0,0 +1,105 @@ +package io.github.lambdatest.gradle.integration; + +import static org.junit.jupiter.api.Assertions.*; + +import io.github.lambdatest.gradle.TestExecutor; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ExecutionIntegrationTest { + private MockLambdaTestServer mockServer; + + @BeforeEach + void setUp() throws IOException { + mockServer = new MockLambdaTestServer(); + mockServer.start(); + } + + @AfterEach + void tearDown() throws IOException { + mockServer.stop(); + } + + @Test + void testEspressoTestExecution() throws Exception { + // Arrange + String buildId = "BUILD123456789"; + mockServer.enqueueBuildResponse(buildId); + + TestExecutor executor = + new TestExecutor( + "testuser", + "testkey", + "lt://APP123", + "lt://TEST123", + Arrays.asList("Pixel 3-9"), + false); + + Map params = new HashMap<>(); + params.put("build", "Test Build"); + params.put("video", "true"); + + // Act + executor.executeTests(params); + + // Assert + RecordedRequest request = mockServer.takeRequest(); + assertEquals("POST", request.getMethod()); + assertTrue(request.getPath().contains("espresso/build")); + assertNotNull(request.getHeader("Authorization")); + assertTrue(request.getBody().readUtf8().contains("Test Build")); + } + + @Test + void testFlutterTestExecution() throws Exception { + // Arrange + String buildId = "FLUTTER_BUILD123456789"; + mockServer.enqueueBuildResponse(buildId); + + TestExecutor executor = + new TestExecutor( + "testuser", + "testkey", + "lt://APP123", + "lt://TEST123", + Arrays.asList("Pixel 3-9"), + true); + + // Act + executor.executeTests(new HashMap<>()); + + // Assert + RecordedRequest request = mockServer.takeRequest(); + assertEquals("POST", request.getMethod()); + assertTrue(request.getPath().contains("flutter/build")); + } + + @Test + void testExecutionWithInvalidCredentials() throws Exception { + // Arrange + mockServer.enqueueErrorResponse(401, "Invalid credentials"); + + TestExecutor executor = + new TestExecutor( + "baduser", + "badkey", + "lt://APP123", + "lt://TEST123", + Arrays.asList("Pixel 3-9"), + false); + + // Act + executor.executeTests(new HashMap<>()); + + // Assert - Verify request was made even with invalid credentials + RecordedRequest request = mockServer.takeRequest(); + assertEquals("POST", request.getMethod()); + assertTrue(request.getPath().contains("espresso/build")); + } +} diff --git a/src/test/java/io/github/lambdatest/gradle/integration/FullWorkflowTest.java b/src/test/java/io/github/lambdatest/gradle/integration/FullWorkflowTest.java new file mode 100644 index 0000000..c2301a6 --- /dev/null +++ b/src/test/java/io/github/lambdatest/gradle/integration/FullWorkflowTest.java @@ -0,0 +1,126 @@ +package io.github.lambdatest.gradle.integration; + +import static org.junit.jupiter.api.Assertions.*; + +import io.github.lambdatest.gradle.AppUploader; +import io.github.lambdatest.gradle.TestExecutor; +import io.github.lambdatest.gradle.TestSuiteUploader; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.concurrent.CompletableFuture; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class FullWorkflowTest { + private MockLambdaTestServer mockServer; + private String testAppPath; + private String testSuitePath; + + @BeforeEach + void setUp() throws IOException { + mockServer = new MockLambdaTestServer(); + mockServer.start(); + + testAppPath = getClass().getClassLoader().getResource("test-app.apk").getPath(); + testSuitePath = getClass().getClassLoader().getResource("test-suite.apk").getPath(); + } + + @AfterEach + void tearDown() throws IOException { + mockServer.stop(); + } + + @Test + void testFullWorkflow() throws Exception { + // Arrange - Mock responses for upload and execution + mockServer.enqueueUploadResponse("lt://APP123456789"); + mockServer.enqueueUploadResponse("lt://TEST123456789"); + mockServer.enqueueBuildResponse("BUILD123456789"); + + // Act - Upload app and test suite + AppUploader appUploader = new AppUploader("testuser", "testkey", testAppPath); + TestSuiteUploader testUploader = + new TestSuiteUploader("testuser", "testkey", testSuitePath); + + CompletableFuture appIdFuture = appUploader.uploadAppAsync(); + CompletableFuture testIdFuture = testUploader.uploadTestSuiteAsync(); + + String appId = appIdFuture.get(); + String testId = testIdFuture.get(); + + // Execute tests + TestExecutor executor = + new TestExecutor( + "testuser", "testkey", appId, testId, Arrays.asList("Pixel 3-9"), false); + executor.executeTests(new HashMap<>()); + + // Assert - Verify all three requests were made + RecordedRequest appUploadRequest = mockServer.takeRequest(); + assertEquals("POST", appUploadRequest.getMethod()); + assertTrue(appUploadRequest.getPath().contains("uploadFramework")); + + RecordedRequest testUploadRequest = mockServer.takeRequest(); + assertEquals("POST", testUploadRequest.getMethod()); + assertTrue(testUploadRequest.getPath().contains("uploadFramework")); + + RecordedRequest executionRequest = mockServer.takeRequest(); + assertEquals("POST", executionRequest.getMethod()); + assertTrue(executionRequest.getPath().contains("espresso/build")); + } + + @Test + void testUploadOnlyWorkflow() throws Exception { + // Arrange + mockServer.enqueueUploadResponse("lt://APP123456789"); + mockServer.enqueueUploadResponse("lt://TEST123456789"); + + // Act + AppUploader appUploader = new AppUploader("testuser", "testkey", testAppPath); + TestSuiteUploader testUploader = + new TestSuiteUploader("testuser", "testkey", testSuitePath); + + CompletableFuture appIdFuture = appUploader.uploadAppAsync(); + CompletableFuture testIdFuture = testUploader.uploadTestSuiteAsync(); + + String appId = appIdFuture.get(); + String testId = testIdFuture.get(); + + // Assert - Since uploads are async, either could get either ID + assertTrue(appId.equals("lt://APP123456789") || appId.equals("lt://TEST123456789")); + assertTrue(testId.equals("lt://APP123456789") || testId.equals("lt://TEST123456789")); + assertNotEquals(appId, testId); // They should be different + + RecordedRequest appUploadRequest = mockServer.takeRequest(); + assertEquals("POST", appUploadRequest.getMethod()); + assertTrue(appUploadRequest.getPath().contains("uploadFramework")); + + RecordedRequest testUploadRequest = mockServer.takeRequest(); + assertEquals("POST", testUploadRequest.getMethod()); + assertTrue(testUploadRequest.getPath().contains("uploadFramework")); + } + + @Test + void testExecutionWithExistingIds() throws Exception { + // Arrange + mockServer.enqueueBuildResponse("BUILD123456789"); + + // Act + TestExecutor executor = + new TestExecutor( + "testuser", + "testkey", + "lt://EXISTING_APP", + "lt://EXISTING_TEST", + Arrays.asList("Pixel 3-9"), + false); + executor.executeTests(new HashMap<>()); + + // Assert + RecordedRequest executionRequest = mockServer.takeRequest(); + assertEquals("POST", executionRequest.getMethod()); + assertTrue(executionRequest.getPath().contains("espresso/build")); + } +} diff --git a/src/test/java/io/github/lambdatest/gradle/integration/MockLambdaTestServer.java b/src/test/java/io/github/lambdatest/gradle/integration/MockLambdaTestServer.java new file mode 100644 index 0000000..6159b9c --- /dev/null +++ b/src/test/java/io/github/lambdatest/gradle/integration/MockLambdaTestServer.java @@ -0,0 +1,59 @@ +package io.github.lambdatest.gradle.integration; + +import io.github.lambdatest.gradle.Constants; +import java.io.IOException; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + +/** Utility class for setting up MockWebServer to simulate LambdaTest API responses. */ +public class MockLambdaTestServer { + private MockWebServer server; + private String baseUrl; + + public void start() throws IOException { + server = new MockWebServer(); + server.start(); + baseUrl = server.url("/").toString(); + + // Configure Constants to use mock URLs + Constants.setTestUrls( + baseUrl + "app/uploadFramework", + baseUrl + "framework/v1/espresso/build", + baseUrl + "framework/v1/flutter/build"); + } + + public void stop() throws IOException { + if (server != null) { + server.shutdown(); + Constants.resetUrls(); + } + } + + public void enqueueUploadResponse(String appId) { + server.enqueue( + new MockResponse().setResponseCode(200).setBody("{\"app_id\":\"" + appId + "\"}")); + } + + public void enqueueBuildResponse(String buildId) { + server.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody("{\"build_id\":\"" + buildId + "\",\"status\":\"success\"}")); + } + + public void enqueueErrorResponse(int code, String message) { + server.enqueue( + new MockResponse() + .setResponseCode(code) + .setBody("{\"error\":\"" + message + "\"}")); + } + + public RecordedRequest takeRequest() throws InterruptedException { + return server.takeRequest(); + } + + public String getBaseUrl() { + return baseUrl; + } +} diff --git a/src/test/java/io/github/lambdatest/gradle/integration/UploadIntegrationTest.java b/src/test/java/io/github/lambdatest/gradle/integration/UploadIntegrationTest.java new file mode 100644 index 0000000..21431ab --- /dev/null +++ b/src/test/java/io/github/lambdatest/gradle/integration/UploadIntegrationTest.java @@ -0,0 +1,85 @@ +package io.github.lambdatest.gradle.integration; + +import static org.junit.jupiter.api.Assertions.*; + +import io.github.lambdatest.gradle.AppUploader; +import io.github.lambdatest.gradle.TestSuiteUploader; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class UploadIntegrationTest { + private MockLambdaTestServer mockServer; + private String testAppPath; + private String testSuitePath; + + @BeforeEach + void setUp() throws IOException { + mockServer = new MockLambdaTestServer(); + mockServer.start(); + + testAppPath = getClass().getClassLoader().getResource("test-app.apk").getPath(); + testSuitePath = getClass().getClassLoader().getResource("test-suite.apk").getPath(); + } + + @AfterEach + void tearDown() throws IOException { + mockServer.stop(); + } + + @Test + void testAppUploadSuccess() throws Exception { + // Arrange + String expectedAppId = "lt://APP123456789"; + mockServer.enqueueUploadResponse(expectedAppId); + + AppUploader uploader = new AppUploader("testuser", "testkey", testAppPath); + + // Act + CompletableFuture result = uploader.uploadAppAsync(); + String actualAppId = result.get(); + + // Assert + assertEquals(expectedAppId, actualAppId); + + RecordedRequest request = mockServer.takeRequest(); + assertEquals("POST", request.getMethod()); + assertTrue(request.getPath().contains("uploadFramework")); + assertNotNull(request.getHeader("Authorization")); + } + + @Test + void testTestSuiteUploadSuccess() throws Exception { + // Arrange + String expectedTestSuiteId = "lt://TEST123456789"; + mockServer.enqueueUploadResponse(expectedTestSuiteId); + + TestSuiteUploader uploader = new TestSuiteUploader("testuser", "testkey", testSuitePath); + + // Act + CompletableFuture result = uploader.uploadTestSuiteAsync(); + String actualTestSuiteId = result.get(); + + // Assert + assertEquals(expectedTestSuiteId, actualTestSuiteId); + + RecordedRequest request = mockServer.takeRequest(); + assertEquals("POST", request.getMethod()); + assertTrue(request.getPath().contains("uploadFramework")); + } + + @Test + void testUploadFailure() { + // Arrange + mockServer.enqueueErrorResponse(401, "Unauthorized"); + + AppUploader uploader = new AppUploader("baduser", "badkey", testAppPath); + + // Act & Assert + CompletableFuture result = uploader.uploadAppAsync(); + assertThrows(Exception.class, result::get); + } +} diff --git a/src/test/resources/test-app.apk b/src/test/resources/test-app.apk new file mode 100644 index 0000000..2c0057e --- /dev/null +++ b/src/test/resources/test-app.apk @@ -0,0 +1 @@ +PK \ No newline at end of file diff --git a/src/test/resources/test-suite.apk b/src/test/resources/test-suite.apk new file mode 100644 index 0000000..2c0057e --- /dev/null +++ b/src/test/resources/test-suite.apk @@ -0,0 +1 @@ +PK \ No newline at end of file