|
| 1 | +# S3Mock Development Guidelines (Concise) |
| 2 | + |
| 3 | +Essential info for working on S3Mock. This top section is a quick, no‑frills guide. Details follow below. |
| 4 | + |
| 5 | +Quickstart TL;DR |
| 6 | +- Build (fast): ./mvnw clean install -DskipDocker |
| 7 | +- Build (full): ./mvnw clean install |
| 8 | +- Server tests only: ./mvnw -pl server test |
| 9 | +- All tests incl. ITs (requires Docker): ./mvnw verify |
| 10 | +- One server test: ./mvnw -pl server test -Dtest=ObjectStoreTest |
| 11 | +- One IT: ./mvnw -pl integration-tests -am verify -Dit.test=BucketIT |
| 12 | + |
| 13 | +Requirements |
| 14 | +- JDK 17+ |
| 15 | +- Docker (only for integration tests and Docker image build) |
| 16 | + |
| 17 | +Common config (env vars) |
| 18 | +- COM_ADOBE_TESTING_S3MOCK_STORE_REGION (default: us-east-1) |
| 19 | +- COM_ADOBE_TESTING_S3MOCK_STORE_INITIAL_BUCKETS |
| 20 | +- COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT (default: false) |
| 21 | +- debug / trace (Spring Boot flags) |
| 22 | + |
| 23 | +Tips |
| 24 | +- IDE runs: use server tests; ITs need Docker via Maven lifecycle. |
| 25 | +- Debug server on 9090/9191; trust self‑signed cert or use HTTP. |
| 26 | +- Before declaring done: run a full Maven build successfully. |
| 27 | + |
| 28 | +Troubleshooting |
| 29 | +- Connection refused: ensure S3Mock is running on expected ports. |
| 30 | +- SSL errors: trust self‑signed cert or switch to HTTP. |
| 31 | +- Docker errors: ensure Docker is running and you have permissions. |
| 32 | + |
| 33 | +— |
| 34 | + |
| 35 | +## Build and Configuration Instructions |
| 36 | + |
| 37 | +### Building the Project |
| 38 | + |
| 39 | +S3Mock uses Maven for building. The project includes a Maven wrapper (`mvnw`) so you don't need to install Maven separately. |
| 40 | + |
| 41 | +#### Basic Build Commands |
| 42 | + |
| 43 | +```bash |
| 44 | +# Build the entire project |
| 45 | +./mvnw clean install |
| 46 | + |
| 47 | +# Build without running Docker (useful for quick builds) |
| 48 | +./mvnw clean install -DskipDocker |
| 49 | + |
| 50 | +# Build a specific module |
| 51 | +./mvnw clean install -pl server -am |
| 52 | +``` |
| 53 | + |
| 54 | +#### Build Requirements |
| 55 | + |
| 56 | +- JDK 17 or higher |
| 57 | +- Docker (for building the Docker image and running integration tests) |
| 58 | + |
| 59 | +### Configuration Options |
| 60 | + |
| 61 | +S3Mock can be configured with the following environment variables: |
| 62 | + |
| 63 | +| Environment Variable | Legacy Name | Description | Default | |
| 64 | +|-------------------------------------------------------|-----------------------------------|-------------------------------------------------------|---------------------| |
| 65 | +| `COM_ADOBE_TESTING_S3MOCK_STORE_VALID_KMS_KEYS` | `validKmsKeys` | List of KMS Key-Refs that are treated as valid | none | |
| 66 | +| `COM_ADOBE_TESTING_S3MOCK_STORE_INITIAL_BUCKETS` | `initialBuckets` | List of bucket names that will be available initially | none | |
| 67 | +| `COM_ADOBE_TESTING_S3MOCK_STORE_REGION` | `COM_ADOBE_TESTING_S3MOCK_REGION` | The region the S3Mock is supposed to mock | `us-east-1` | |
| 68 | +| `COM_ADOBE_TESTING_S3MOCK_STORE_ROOT` | `root` | Base directory for temporary files | Java temp directory | |
| 69 | +| `COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT` | `retainFilesOnExit` | Set to `true` to keep files after shutdown | `false` | |
| 70 | +| `debug` | - | Enable Spring Boot's debug output | `false` | |
| 71 | +| `trace` | - | Enable Spring Boot's trace output | `false` | |
| 72 | + |
| 73 | +## Testing Information |
| 74 | + |
| 75 | +### Test Structure |
| 76 | + |
| 77 | +The project uses JUnit 5 for testing. There are two main types of tests in the project: |
| 78 | + |
| 79 | +1. **Integration Tests**: Located in the `integration-tests` module and written in Kotlin. These tests verify the end-to-end functionality of S3Mock by starting a Docker container and making actual HTTP requests to it. |
| 80 | + |
| 81 | +2. **Server Module Tests**: Located in the `server` module and primarily written in Kotlin. These include: |
| 82 | + - Unit tests for utility classes and DTOs |
| 83 | + - Spring Boot tests for controllers, services, and stores |
| 84 | + - XML serialization/deserialization tests |
| 85 | + |
| 86 | +#### Integration Tests |
| 87 | + |
| 88 | +The main test base class for integration tests is `S3TestBase` which provides utility methods for: |
| 89 | +- Creating S3 clients |
| 90 | +- Managing buckets and objects |
| 91 | +- Handling SSL certificates |
| 92 | +- Generating test data |
| 93 | + |
| 94 | +#### Server Module Tests |
| 95 | + |
| 96 | +The server module contains several types of tests: |
| 97 | + |
| 98 | +1. **Controller Tests**: Use `@SpringBootTest` with `WebEnvironment.RANDOM_PORT` and `TestRestTemplate` to test HTTP endpoints. These tests mock the service layer using `@MockBean`. |
| 99 | + |
| 100 | +2. **Store Tests**: Use `@SpringBootTest` with `WebEnvironment.NONE` to test the data storage layer. These tests often use `@Autowired` to inject the component under test. |
| 101 | + |
| 102 | +3. **Service Tests**: Test the service layer with mocked dependencies. |
| 103 | + |
| 104 | +4. **Unit Tests**: Simple tests for utility classes and DTOs without Spring context. |
| 105 | + |
| 106 | +Common base classes and utilities: |
| 107 | +- `BaseControllerTest`: Sets up XML serialization/deserialization for controller tests |
| 108 | +- `StoreTestBase`: Common setup for store tests |
| 109 | +- `ServiceTestBase`: Common setup for service tests |
| 110 | + |
| 111 | +### Running Tests |
| 112 | + |
| 113 | +#### Running Server Module Tests |
| 114 | + |
| 115 | +The server module tests can be run without Docker: |
| 116 | + |
| 117 | +```bash |
| 118 | +# Run all server module tests |
| 119 | +./mvnw -pl server test |
| 120 | + |
| 121 | +# Run a specific test class |
| 122 | +./mvnw -pl server test -Dtest=ObjectStoreTest |
| 123 | + |
| 124 | +# Run a specific test method |
| 125 | +./mvnw -pl server test -Dtest=ObjectStoreTest#testStoreObject |
| 126 | +``` |
| 127 | + |
| 128 | +The server module uses Spring Test Profiler to analyze test performance. Test execution times are reported in the `target/spring-test-profiler` directory. |
| 129 | + |
| 130 | +#### Running Integration Tests |
| 131 | + |
| 132 | +Integration tests require Docker to be running as they start an S3Mock Docker container. The integration tests can only be executed through Maven because they need the S3Mock to run. The process works as follows: |
| 133 | + |
| 134 | +1. The Docker Maven plugin (io.fabric8:docker-maven-plugin) starts the S3Mock Docker container in the pre-integration-test phase |
| 135 | +2. The Maven Failsafe plugin runs the integration tests against the running container |
| 136 | +3. The Docker Maven plugin stops the container in the post-integration-test phase |
| 137 | + |
| 138 | +```bash |
| 139 | +# Run all integration tests |
| 140 | +./mvnw verify |
| 141 | + |
| 142 | +# Run a specific integration test |
| 143 | +./mvnw -pl integration-tests -am verify -Dit.test=BucketIT |
| 144 | + |
| 145 | +# Run tests without Docker (will skip integration tests) |
| 146 | +./mvnw verify -DskipDocker |
| 147 | +``` |
| 148 | + |
| 149 | +Note that attempting to run integration tests directly from your IDE without starting the S3Mock Docker container will result in connection errors, as the tests expect S3Mock to be running on specific ports. |
| 150 | + |
| 151 | +### Writing New Tests |
| 152 | + |
| 153 | +#### Writing Integration Tests |
| 154 | + |
| 155 | +To create a new integration test: |
| 156 | + |
| 157 | +1. Create a new Kotlin class in the `integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its` directory |
| 158 | +2. Extend the `S3TestBase` class to inherit utility methods |
| 159 | +3. Name your test class with an `IT` suffix to be recognized as an integration test |
| 160 | +4. Use the provided S3 client methods to interact with S3Mock |
| 161 | + |
| 162 | +Example integration test: |
| 163 | + |
| 164 | +```kotlin |
| 165 | +// ExampleIT.kt |
| 166 | +internal class ExampleIT : S3TestBase() { |
| 167 | + private val s3Client = createS3Client() |
| 168 | + |
| 169 | + @Test |
| 170 | + fun testPutAndGetObject(testInfo: TestInfo) { |
| 171 | + // Create a bucket |
| 172 | + val bucketName = givenBucket(testInfo) |
| 173 | + |
| 174 | + // Create test content |
| 175 | + val content = "This is a test file content" |
| 176 | + val contentBytes = content.toByteArray(StandardCharsets.UTF_8) |
| 177 | + |
| 178 | + // Put an object into the bucket |
| 179 | + val key = "test-object.txt" |
| 180 | + val putObjectResponse = s3Client.putObject( |
| 181 | + PutObjectRequest.builder() |
| 182 | + .bucket(bucketName) |
| 183 | + .key(key) |
| 184 | + .build(), |
| 185 | + RequestBody.fromBytes(contentBytes) |
| 186 | + ) |
| 187 | + |
| 188 | + // Verify the object was uploaded successfully |
| 189 | + assertThat(putObjectResponse.eTag()).isNotBlank() |
| 190 | + |
| 191 | + // Get the object back |
| 192 | + val getObjectResponse = s3Client.getObject( |
| 193 | + GetObjectRequest.builder() |
| 194 | + .bucket(bucketName) |
| 195 | + .key(key) |
| 196 | + .build() |
| 197 | + ) |
| 198 | + |
| 199 | + // Read the content and verify it matches what we uploaded |
| 200 | + val retrievedContent = getObjectResponse.readAllBytes() |
| 201 | + assertThat(retrievedContent).isEqualTo(contentBytes) |
| 202 | + |
| 203 | + // Clean up |
| 204 | + s3Client.deleteObject { it.bucket(bucketName).key(key) } |
| 205 | + } |
| 206 | +} |
| 207 | +``` |
| 208 | + |
| 209 | +#### Writing Server Module Tests |
| 210 | + |
| 211 | +The server module uses different testing approaches depending on what's being tested: |
| 212 | + |
| 213 | +1. **Controller Tests**: |
| 214 | + - Extend `BaseControllerTest` to inherit XML serialization setup |
| 215 | + - Use `@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)` |
| 216 | + - Use `@MockBean` to mock service dependencies |
| 217 | + - Inject `TestRestTemplate` to make HTTP requests to the controller |
| 218 | + |
| 219 | +Example controller test: |
| 220 | + |
| 221 | +```kotlin |
| 222 | +// BucketControllerTest.kt |
| 223 | +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) |
| 224 | +@MockBean(classes = [BucketService::class, ObjectService::class, MultipartService::class]) |
| 225 | +internal class BucketControllerTest : BaseControllerTest() { |
| 226 | + @Autowired |
| 227 | + private lateinit var restTemplate: TestRestTemplate |
| 228 | + |
| 229 | + @MockBean |
| 230 | + private lateinit var bucketService: BucketService |
| 231 | + |
| 232 | + @Test |
| 233 | + fun testListBuckets() { |
| 234 | + // Mock service response |
| 235 | + whenever(bucketService.listBuckets()).thenReturn(givenBuckets(2)) |
| 236 | + |
| 237 | + // Make HTTP request |
| 238 | + val response = restTemplate.getForEntity("/", ListAllMyBucketsResult::class.java) |
| 239 | + |
| 240 | + // Verify response |
| 241 | + assertThat(response.statusCode).isEqualTo(HttpStatus.OK) |
| 242 | + assertThat(response.body.buckets.bucket).hasSize(2) |
| 243 | + } |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +2. **Store Tests**: |
| 248 | + - Extend `StoreTestBase` for common setup |
| 249 | + - Use `@SpringBootTest(webEnvironment = WebEnvironment.NONE)` |
| 250 | + - Use `@Autowired` to inject the component under test |
| 251 | + |
| 252 | +Example store test: |
| 253 | + |
| 254 | +```kotlin |
| 255 | +// ObjectStoreTest.kt |
| 256 | +@SpringBootTest(classes = [StoreConfiguration::class], webEnvironment = SpringBootTest.WebEnvironment.NONE) |
| 257 | +@MockBean(classes = [KmsKeyStore::class, BucketStore::class]) |
| 258 | +internal class ObjectStoreTest : StoreTestBase() { |
| 259 | + @Autowired |
| 260 | + private lateinit var objectStore: ObjectStore |
| 261 | + |
| 262 | + @Test |
| 263 | + fun testStoreAndGetObject() { |
| 264 | + // Test storing and retrieving an object |
| 265 | + val sourceFile = File(TEST_FILE_PATH) |
| 266 | + val id = UUID.randomUUID().toString() |
| 267 | + val name = sourceFile.name |
| 268 | + |
| 269 | + objectStore.storeS3ObjectMetadata( |
| 270 | + metadataFrom("test-bucket"), id, name, "text/plain", |
| 271 | + emptyMap(), sourceFile.toPath(), |
| 272 | + emptyMap(), emptyMap(), null, emptyList(), |
| 273 | + null, null, Owner.DEFAULT_OWNER, StorageClass.STANDARD |
| 274 | + ) |
| 275 | + |
| 276 | + val metadata = objectStore.getS3ObjectMetadata(metadataFrom("test-bucket"), id, null) |
| 277 | + |
| 278 | + assertThat(metadata.key).isEqualTo(name) |
| 279 | + assertThat(metadata.contentType).isEqualTo("text/plain") |
| 280 | + } |
| 281 | +} |
| 282 | +``` |
| 283 | + |
| 284 | +3. **Unit Tests**: |
| 285 | + - Use standard JUnit 5 tests without Spring context |
| 286 | + - Focus on testing a single class or method in isolation |
| 287 | + |
| 288 | +Example unit test: |
| 289 | + |
| 290 | +```kotlin |
| 291 | +// DigestUtilTest.kt |
| 292 | +internal class DigestUtilTest { |
| 293 | + @Test |
| 294 | + fun testHexDigest() { |
| 295 | + val input = "test data".toByteArray() |
| 296 | + val expected = DigestUtils.md5Hex(input) |
| 297 | + |
| 298 | + assertThat(DigestUtil.hexDigest(input)).isEqualTo(expected) |
| 299 | + } |
| 300 | +} |
| 301 | +``` |
| 302 | + |
| 303 | +## Additional Development Information |
| 304 | + |
| 305 | +### Project Structure |
| 306 | + |
| 307 | +S3Mock is a multi-module Maven project: |
| 308 | + |
| 309 | +- `build-config`: Build configuration files |
| 310 | +- `docker`: Docker image build module |
| 311 | +- `integration-tests`: Integration tests |
| 312 | +- `server`: Core S3Mock implementation |
| 313 | +- `testsupport`: Test support modules for different testing frameworks |
| 314 | + |
| 315 | +### Code Style |
| 316 | + |
| 317 | +The project uses Checkstyle for Java code style checking. The configuration is in `build-config/checkstyle.xml`. |
| 318 | + |
| 319 | +### License Headers |
| 320 | + |
| 321 | +S3Mock uses the Apache License 2.0 and enforces proper license headers in all source files through the `license-maven-plugin`. Important rules: |
| 322 | + |
| 323 | +- All source files must include the proper license header |
| 324 | +- When modifying a file, the copyright year range in the header must be updated to include the current year |
| 325 | +- The format is: `Copyright 2017-YYYY Adobe.` where YYYY is the current year |
| 326 | +- The license check runs automatically during the build process |
| 327 | +- To fix license headers, run: `./mvnw license:format` |
| 328 | +- To check license headers without modifying files: `./mvnw license:check` |
| 329 | + |
| 330 | +### Debugging |
| 331 | + |
| 332 | +To debug the S3Mock server: |
| 333 | + |
| 334 | +1. Run the S3MockApplication class in your IDE with debug mode |
| 335 | +2. The server will start on ports 9090 (HTTP) and 9191 (HTTPS) |
| 336 | +3. Configure your S3 client to connect to these ports |
| 337 | + |
| 338 | +Alternatively, you can run the Docker container with debug enabled: |
| 339 | + |
| 340 | +```bash |
| 341 | +docker run -p 9090:9090 -p 9191:9191 -e debug=true -t adobe/s3mock |
| 342 | +``` |
| 343 | + |
| 344 | +### Common Issues |
| 345 | + |
| 346 | +1. **Connection refused errors**: Ensure the S3Mock server is running and the ports are correctly configured. |
| 347 | + |
| 348 | +2. **SSL certificate errors**: S3Mock uses a self-signed certificate. Configure your client to trust all certificates or use HTTP instead. |
| 349 | + |
| 350 | +3. **Docker-related errors**: Make sure Docker is running, and you have permissions to create containers. |
| 351 | + |
| 352 | +### Recommended Development Workflow |
| 353 | + |
| 354 | +1. Make changes to the code |
| 355 | +2. Run unit tests frequently to verify basic functionality |
| 356 | + - Unit tests should be run very frequently during development |
| 357 | + - No task can be declared as done without running a full Maven build successfully |
| 358 | +3. Run integration tests to verify end-to-end functionality |
| 359 | +4. Build the Docker image to verify packaging |
| 360 | +5. Test with your application to verify real-world usage |
0 commit comments