diff --git a/docker-compose.yaml b/docker-compose.yaml index 7884376..31a083c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,14 @@ version: '3.8' services: + test: + image: maven:3.9-eclipse-temurin-11 + volumes: + - .:/app + working_dir: /app + # Run only unit tests (no real credentials). Full suite: mvn test with env set. + command: mvn test -Dtest=ProjectsClientTest,AwsAuthProviderTest + format: image: cimg/openjdk:21.0 volumes: diff --git a/pom.xml b/pom.xml index 9ad79c7..024aa82 100644 --- a/pom.xml +++ b/pom.xml @@ -151,6 +151,18 @@ 5.9.1 test + + org.mockito + mockito-core + 5.5.0 + test + + + org.mockito + mockito-junit-jupiter + 5.5.0 + test + diff --git a/src/main/java/com/infisical/sdk/InfisicalSdk.java b/src/main/java/com/infisical/sdk/InfisicalSdk.java index b01228f..cac4701 100644 --- a/src/main/java/com/infisical/sdk/InfisicalSdk.java +++ b/src/main/java/com/infisical/sdk/InfisicalSdk.java @@ -4,11 +4,13 @@ import com.infisical.sdk.config.SdkConfig; import com.infisical.sdk.resources.AuthClient; import com.infisical.sdk.resources.FoldersClient; +import com.infisical.sdk.resources.ProjectsClient; import com.infisical.sdk.resources.SecretsClient; public class InfisicalSdk { private SecretsClient secretsClient; private FoldersClient foldersClient; + private ProjectsClient projectsClient; private AuthClient authClient; private ApiClient apiClient; @@ -26,6 +28,7 @@ private void onAuthenticate(String accessToken) { this.secretsClient = new SecretsClient(apiClient); this.foldersClient = new FoldersClient(apiClient); + this.projectsClient = new ProjectsClient(apiClient); this.authClient = new AuthClient(apiClient, this::onAuthenticate); } @@ -40,4 +43,8 @@ public SecretsClient Secrets() { public FoldersClient Folders() { return this.foldersClient; } + + public ProjectsClient Projects() { + return this.projectsClient; + } } diff --git a/src/main/java/com/infisical/sdk/models/Project.java b/src/main/java/com/infisical/sdk/models/Project.java new file mode 100644 index 0000000..3b15abc --- /dev/null +++ b/src/main/java/com/infisical/sdk/models/Project.java @@ -0,0 +1,23 @@ +package com.infisical.sdk.models; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +public class Project { + + @SerializedName("id") + private String id; + + @SerializedName("name") + private String name; + + @SerializedName("slug") + private String slug; + + @SerializedName("orgId") + private String orgId; + + @SerializedName("description") + private String description; +} diff --git a/src/main/java/com/infisical/sdk/resources/ProjectsClient.java b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java new file mode 100644 index 0000000..e8b5ba7 --- /dev/null +++ b/src/main/java/com/infisical/sdk/resources/ProjectsClient.java @@ -0,0 +1,34 @@ +package com.infisical.sdk.resources; + +import com.infisical.sdk.api.ApiClient; +import com.infisical.sdk.models.Project; +import com.infisical.sdk.util.Helper; +import com.infisical.sdk.util.InfisicalException; + +public class ProjectsClient { + private final ApiClient apiClient; + + public ProjectsClient(ApiClient apiClient) { + this.apiClient = apiClient; + } + + /** + * Fetches project details by slug. Requires authentication. + * + * @param slug the project slug (e.g. from the project URL) + * @return the project including id, name, slug, orgId, etc. + * @throws InfisicalException when slug is invalid or the API request fails + */ + public Project GetBySlug(String slug) throws InfisicalException { + if (Helper.isNullOrEmpty(slug)) { + throw new InfisicalException("Project slug is required"); + } + String trimmed = slug.trim(); + + String url = + String.format( + "%s/api/v1/projects/slug/%s", + this.apiClient.GetBaseUrl(), trimmed); + return this.apiClient.get(url, null, Project.class); + } +} diff --git a/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java new file mode 100644 index 0000000..ab033bf --- /dev/null +++ b/src/test/java/com/infisical/sdk/resources/ProjectsClientTest.java @@ -0,0 +1,62 @@ +package com.infisical.sdk.resources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.infisical.sdk.api.ApiClient; +import com.infisical.sdk.models.Project; +import com.infisical.sdk.util.InfisicalException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class ProjectsClientTest { + + @Mock + private ApiClient apiClient; + + @Test + public void GetBySlug_throwsWhenSlugIsNull() { + ProjectsClient client = new ProjectsClient(apiClient); + + InfisicalException ex = assertThrows(InfisicalException.class, () -> client.GetBySlug(null)); + assertEquals("Project slug is required", ex.getMessage()); + } + + @Test + public void GetBySlug_throwsWhenSlugIsEmpty() { + ProjectsClient client = new ProjectsClient(apiClient); + + InfisicalException ex = assertThrows(InfisicalException.class, () -> client.GetBySlug("")); + assertEquals("Project slug is required", ex.getMessage()); + } + + @Test + public void GetBySlug_callsGetWithCorrectUrl() throws InfisicalException { + when(apiClient.GetBaseUrl()).thenReturn("https://app.infisical.com"); + Project project = new Project(); + project.setId("proj-123"); + project.setSlug("my-project"); + when(apiClient.get( + eq("https://app.infisical.com/api/v1/projects/slug/my-project"), + eq(null), + eq(Project.class))) + .thenReturn(project); + + ProjectsClient client = new ProjectsClient(apiClient); + Project result = client.GetBySlug("my-project"); + + assertEquals("proj-123", result.getId()); + assertEquals("my-project", result.getSlug()); + verify(apiClient) + .get( + eq("https://app.infisical.com/api/v1/projects/slug/my-project"), + eq(null), + eq(Project.class)); + } +}