myrelaxsauna.com

Integrating Testcontainers in a Spring Boot LDAP API

Written on

Chapter 1: Overview

This article outlines the steps to configure Testcontainers in a Spring Boot application, enabling the creation of an OpenLDAP Docker container during testing. This setup will help ensure the proper functioning of the LDAP-secured API.

The project we will examine is a straightforward Spring Boot REST API known as spring-boot-ldap-simple-api. For comprehensive code and implementation details, refer to the article linked below. Follow along with the steps provided to get started.

Once you've completed the spring-boot-ldap-simple-api application, we can proceed to implement the testing code.

Section 1.1: Updating the API

To begin, we need to update the pom.xml file. Add the following Testcontainers dependencies by inserting the highlighted sections:

...

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-testcontainers</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.testcontainers</groupId>

<artifactId>junit-jupiter</artifactId>

<scope>test</scope>

</dependency>

...

Section 1.2: Creating the LDIF Test File

In the root directory of your Simple API application, create a file titled test-ldap-mycompany-com.ldif with the following content:

dn: ou=groups,dc=mycompany,dc=com

objectclass: organizationalUnit

objectclass: top

ou: groups

dn: cn=user,ou=groups,dc=mycompany,dc=com

cn: user

gidnumber: 500

objectclass: posixGroup

objectclass: top

dn: ou=users,dc=mycompany,dc=com

objectclass: organizationalUnit

objectclass: top

ou: users

dn: uid=app-user-test,ou=users,dc=mycompany,dc=com

cn: App User Test

gidnumber: 500

givenname: App

homedirectory: /home/users/app-user-test

objectclass: inetOrgPerson

objectclass: posixAccount

objectclass: top

sn: User Test

uid: app-user-test

uidnumber: 1000

userpassword: {MD5}ICy5YqxZB1uWSwcVLSNLcA==

This LDIF (LDAP Directory Interchange Format) file defines the OpenLDAP users with a preset structure for "mycompany.com," including a group called "users" and a user identified as "App User Test," with the username app-user-test and password 123.

Section 1.3: Implementing the SimpleApiControllerTest Class

Next, we will create the SimpleApiControllerTest class in the package com.example.springbootldapsimpleapi located in the src/test/java directory. Populate the class with the following code:

package com.example.springbootldapsimpleapi;

import org.junit.jupiter.api.BeforeAll;

import org.junit.jupiter.api.Test;

import org.junit.jupiter.params.ParameterizedTest;

import org.junit.jupiter.params.provider.Arguments;

import org.junit.jupiter.params.provider.MethodSource;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

import org.springframework.boot.test.web.client.TestRestTemplate;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.test.context.DynamicPropertyRegistry;

import org.springframework.test.context.DynamicPropertySource;

import org.testcontainers.containers.BindMode;

import org.testcontainers.containers.GenericContainer;

import org.testcontainers.junit.jupiter.Container;

import org.testcontainers.junit.jupiter.Testcontainers;

import java.io.IOException;

import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

class SimpleApiControllerTest {

private static final String PUBLIC_URL = "/public";

private static final String SECURED_URL = "/secured";

private static final String APP_USER_TEST_USERNAME = "app-user-test";

private static final String APP_USER_TEST_PASSWORD = "123";

private static final int OPENLDAP_EXPOSED_PORT = 389;

@Autowired

private TestRestTemplate testRestTemplate;

@Container

private static final GenericContainer openldapContainer = new GenericContainer<>("osixia/openldap:1.5.0")

.withNetworkAliases("openldap")

.withEnv("LDAP_ORGANISATION", "MyCompany Inc.")

.withEnv("LDAP_DOMAIN", "mycompany.com")

.withExposedPorts(OPENLDAP_EXPOSED_PORT)

.withFileSystemBind(

System.getProperty("user.dir") + "/test-ldap-mycompany-com.ldif",

"/ldap/ldap-mycompany-com.ldif",

BindMode.READ_ONLY);

@DynamicPropertySource

static void dynamicProperties(DynamicPropertyRegistry registry) {

String openldapUrl = "ldap://localhost:%s".formatted(openldapContainer.getMappedPort(OPENLDAP_EXPOSED_PORT));

registry.add("spring.ldap.urls", () -> openldapUrl);

}

@BeforeAll

static void beforeAll() throws IOException, InterruptedException {

openldapContainer.execInContainer("ldapadd", "-x", "-D", "cn=admin,dc=mycompany,dc=com", "-w", "admin", "-H", "ldap://", "-f", "ldap/ldap-mycompany-com.ldif");

}

@Test

void testPublicEndpoint() {

ResponseEntity<String> responseEntity = testRestTemplate.getForEntity(PUBLIC_URL, String.class);

assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);

assertThat(responseEntity.getBody()).isNotNull();

assertThat(responseEntity.getBody()).isEqualTo("Hi World, I am a public endpoint");

}

@Test

void testSecuredEndpointWithoutAuthentication() {

ResponseEntity<String> responseEntity = testRestTemplate.getForEntity(SECURED_URL, String.class);

assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);

}

@Test

void testSecuredEndpointWithValidAuthentication() {

ResponseEntity<String> responseEntity = testRestTemplate

.withBasicAuth(APP_USER_TEST_USERNAME, APP_USER_TEST_PASSWORD)

.getForEntity(SECURED_URL, String.class);

assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);

assertThat(responseEntity.getBody()).isNotNull();

assertThat(responseEntity.getBody()).isEqualTo("Hi app-user-test, I am a secured endpoint");

}

@ParameterizedTest

@MethodSource("provideInvalidCredentials")

void testSecuredEndpointWithInvalidAuthentication(String username, String password) {

ResponseEntity<String> responseEntity = testRestTemplate

.withBasicAuth(username, password)

.getForEntity(SECURED_URL, String.class);

assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);

}

private static Stream<Arguments> provideInvalidCredentials() {

return Stream.of(

Arguments.of("", ""),

Arguments.of(" ", " "),

Arguments.of(APP_USER_TEST_USERNAME, "invalid_password"),

Arguments.of("invalid_username", APP_USER_TEST_PASSWORD)

);

}

}

The @Testcontainers annotation indicates that Testcontainers should manage the containers associated with this test class. A GenericContainer is used to initialize an OpenLDAP container, where we set parameters such as LDAP_ORGANISATION and LDAP_DOMAIN to configure OpenLDAP. This container is linked to an LDIF file to populate data. The @Container annotation signals that Testcontainers oversees this container.

In the dynamicProperties method, we specify properties for the Spring application context. We set the spring.ldap.urls property to point to the URL of the OpenLDAP container.

The test cases are straightforward. In the first test, testPublicEndpoint, a request is made to the /public endpoint, expecting a 200 OK status and the response "Hi World, I am a public endpoint".

The subsequent tests focus on the /secured endpoint, checking for a 401 UNAUTHORIZED response when no credentials (in testSecuredEndpointWithoutAuthentication) or invalid credentials (in testSecuredEndpointWithInvalidAuthentication) are provided.

Finally, testSecuredEndpointWithValidAuthentication tests access with valid credentials, expecting a 200 OK status and the response "Hi user.test, I am a secured endpoint".

Section 1.4: Running Test Cases

To execute the tests, navigate to the root folder of the Simple API application in the terminal and run the following command:

./mvnw clean test

Before the tests commence, Testcontainers will start an OpenLDAP Docker container. You can view the logs, which indicate the container's creation and startup.

Once the OpenLDAP container is operational, the SimpleApiControllerTest class will configure OpenLDAP using the LDIF test file located in the project's root directory.

After setting up OpenLDAP, the tests will be executed. If everything is functioning correctly, you should see an output similar to:

[INFO] Results:

[INFO]

[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0

[INFO]

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------

Chapter 2: Conclusion

In this article, we explored the seamless integration of Testcontainers within a simple Spring Boot REST API secured by LDAP. Testcontainers facilitated the setup of an OpenLDAP Docker container during testing, providing a secure environment for our API. We thoroughly assessed both public and secured endpoints to confirm the API operates as intended.

Support and Engagement

If you found this article helpful and wish to show your support, consider the following actions:

๐Ÿ‘ Engage by clapping, highlighting, and replying to my story. I would be glad to address any questions you may have.

๐ŸŒ Share my story on social media.

๐Ÿ”” Follow me on: Medium | LinkedIn | Twitter | GitHub.

โœ‰๏ธ Subscribe to my newsletter to stay updated with my latest posts.

Chapter 3: Video Resources

Description: This video titled "Spring Boot Testcontainers - Integration Testing made easy!" provides insights into efficiently using Testcontainers for integration testing in Spring Boot applications.

Description: In this video, "Testcontainers and Spring Boot from integration tests to local development!" Oleg ล elajev discusses how to leverage Testcontainers for both integration tests and local development in Spring Boot projects.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

# Understanding Class Dynamics in Jane Austen's

An exploration of class dynamics in Austen's

Transhumanism: Navigating the Promises and Perils of AI

An exploration of Transhumanism, its ethical implications, and the future of AI through various perspectives.

Embracing Your Place in the World: A Journey to Self-Worth

Explore the journey to self-acceptance and the importance of recognizing your worth in the world.