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));
+ }
+}