Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/document/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Read `AGENTS.md` (root + relevant module) before making changes.
|------|----------|---------|
| `README.md` | End users | Usage, configuration, operations table |
| `CHANGELOG.md` | End users | Version history, breaking changes |
| `docs/TESTING.md` | Contributors / AI agents | Testing strategy, base classes, patterns, commands |
| `AGENTS.md` (root + modules) | AI agents | Architecture, conventions, guardrails |
| `.github/CONTRIBUTING.md` | Contributors | Dev setup, code reviews |

Expand Down
34 changes: 3 additions & 31 deletions .claude/skills/test/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,9 @@ description: Write, update, or fix tests. Use when asked to test code, create te

# Test Skill — S3Mock

Read `AGENTS.md` (root + relevant module) before writing tests — especially the Testing, DO/DON'T sections and base classes.
Read **[docs/TESTING.md](../../../docs/TESTING.md)** and `AGENTS.md` (root + relevant module) before writing tests — they define test types, base classes, naming conventions, and running commands.

## Test Types

### Unit Tests (`*Test.kt`) — `server/src/test/kotlin/.../`
- `@SpringBootTest` with `@MockitoBean` for mocking (see AGENTS.md for details)
- Extend the appropriate base class: `ServiceTestBase`, `StoreTestBase`, or `BaseControllerTest`
- Name the class under test `iut`
- Use `@Autowired` for the class under test

### Integration Tests (`*IT.kt`) — `integration-tests/src/test/kotlin/.../its/`
- Extend `S3TestBase` for pre-configured `s3Client` (AWS SDK v2)
- Use `givenBucket(testInfo)` for unique bucket names
- Tests run against Docker container — Docker must be running

## Key Conventions (see AGENTS.md for full list)
## Key Conventions (from AGENTS.md)

- **Naming**: Backtick names: `` fun `should create bucket successfully`() ``
- **Pattern**: Arrange-Act-Assert
Expand All @@ -29,24 +16,9 @@ Read `AGENTS.md` (root + relevant module) before writing tests — especially th
- **Error cases**: `assertThatThrownBy { ... }.isInstanceOf(AwsServiceException::class.java)`
- **Visibility**: `internal class`

## Running Tests

```bash
./mvnw test -pl server # Unit tests
./mvnw verify -pl integration-tests # All integration tests
./mvnw verify -pl integration-tests -Dit.test=BucketIT # Specific class
./mvnw test -pl server -DskipDocker # Skip Docker
```

## Troubleshooting

- **Docker not running**: Integration tests require Docker
- **Port conflict**: Check `lsof -i :9090`
- **Flaky test**: Check for shared state or ordering dependencies
- **Compilation error**: Run `./mvnw clean install -DskipDocker -DskipTests` first

## Checklist

- [ ] Read `docs/TESTING.md` and root + module `AGENTS.md`
- [ ] Tests pass locally
- [ ] Both success and failure cases covered
- [ ] Tests are independent (no shared state, UUID bucket names)
Expand Down
10 changes: 1 addition & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,7 @@ See `server/AGENTS.md` for details.

## Testing

- Unit tests: `@SpringBootTest` with `@MockitoBean`, suffix `Test`
- Integration tests: Real AWS SDK v2 against Docker container, suffix `IT`
- Test independence: Each test self-contained
- Name the class under test **`iut`** (implementation under test): `private lateinit var iut: ObjectService`
- **Base classes** — always extend the appropriate base:
- `ServiceTestBase` for service tests
- `StoreTestBase` for store tests
- `BaseControllerTest` for controller tests
- `S3TestBase` for integration tests
See **[docs/TESTING.md](docs/TESTING.md)** for the full testing strategy, base classes, patterns, and commands.

## Build

Expand Down
106 changes: 106 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Testing Strategy — S3Mock

## Test Types

| Type | Location | Suffix | Purpose |
|------|----------|--------|---------|
| Unit tests | `server/src/test/kotlin/` | `*Test.kt` | Service, store, and controller logic in isolation |
| Integration tests | `integration-tests/src/test/kotlin/.../its/` | `*IT.kt` | Real AWS SDK v2 against a live Docker container |

## Unit Tests

Use `@SpringBootTest` with `@MockitoBean` for mocking. Extend the appropriate base class:

| Base Class | Use For |
|---|---|
| `ServiceTestBase` | Service-layer tests |
| `StoreTestBase` | Store-layer tests |
| `BaseControllerTest` | Controller slice tests (`@WebMvcTest`) |

Name the class under test **`iut`** (implementation under test); inject with `@Autowired`:

```kotlin
@SpringBootTest(classes = [ServiceConfiguration::class], webEnvironment = SpringBootTest.WebEnvironment.NONE)
@MockitoBean(types = [BucketService::class, MultipartService::class, MultipartStore::class])
internal class ObjectServiceTest : ServiceTestBase() {
@Autowired
private lateinit var iut: ObjectService

@Test
fun `should get object`() {
whenever(bucketStore.getBucketMetadata("bucket")).thenReturn(bucket)
whenever(objectStore.getObject(bucket, "key")).thenReturn(s3Object)
assertThat(iut.getObject("bucket", "key")).isEqualTo(s3Object)
}
}
```

## Integration Tests

Extend `S3TestBase` for a pre-configured `s3Client` (AWS SDK v2). Accept `TestInfo` as a method parameter and use `givenBucket(testInfo)` for unique bucket names:

```kotlin
internal class MyFeatureIT : S3TestBase() {
@Test
fun `should perform operation`(testInfo: TestInfo) {
// Arrange
val bucketName = givenBucket(testInfo)

// Act
s3Client.putObject(
PutObjectRequest.builder().bucket(bucketName).key("key").build(),
RequestBody.fromString("content")
)

// Assert
val response = s3Client.getObject(
GetObjectRequest.builder().bucket(bucketName).key("key").build()
)
assertThat(response.readAllBytes().decodeToString()).isEqualTo("content")
}
}
```

Access `serviceEndpoint`, `serviceEndpointHttp`, and `serviceEndpointHttps` from `S3TestBase` when needed.

## Conventions

- **Naming**: Backtick names with descriptive sentences — `` fun `should create bucket successfully`() ``
- **Visibility**: Mark test classes `internal`
- **Pattern**: Arrange-Act-Assert
- **Assertions**: AssertJ (`assertThat(...)`) — use specific matchers, not just `isNotNull()`
- **Error cases**: Use AssertJ, not JUnit `assertThrows`:
```kotlin
assertThatThrownBy { s3Client.deleteBucket { it.bucket(bucketName) } }
.isInstanceOf(AwsServiceException::class.java)
.hasMessageContaining("Status Code: 409")
```
- **Independence**: Each test creates its own resources — no shared state, UUID-based bucket names
- **Legacy names**: Refactor `testSomething` camelCase names to backtick style when touching existing tests

## Running Tests

```bash
./mvnw test -pl server # Unit tests only
./mvnw verify -pl integration-tests # All integration tests
./mvnw verify -pl integration-tests -Dit.test=BucketIT # Specific class
./mvnw verify -pl integration-tests -Dit.test=BucketIT#shouldCreateBucket # Specific method
./mvnw test -pl server -DskipDocker # Skip Docker
```

> Integration tests require Docker to be running.

## Troubleshooting

- **Docker not running**: Integration tests will fail — start Docker first
- **Port conflict**: Check `lsof -i :9090`
- **Flaky test**: Look for shared state or ordering dependencies
- **Compilation error**: Run `./mvnw clean install -DskipDocker -DskipTests` first

## Checklist

- [ ] Tests pass locally
- [ ] Both success and failure cases covered
- [ ] Tests are independent (no shared state, UUID bucket names)
- [ ] Assertions are specific
- [ ] Run `./mvnw ktlint:format`
38 changes: 1 addition & 37 deletions integration-tests/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> Inherits all conventions from the [root AGENTS.md](../AGENTS.md). Below are module-specific additions only.

Integration tests verifying S3Mock with real AWS SDK v2 clients.
Integration tests verifying S3Mock with real AWS SDK v2 clients. See **[docs/TESTING.md](../docs/TESTING.md)** for the full testing strategy, conventions, patterns, and running commands.

## Structure

Expand All @@ -23,45 +23,9 @@ Extend `S3TestBase` for access to:
- Extend **`S3TestBase`** for all integration tests
- Accept **`testInfo: TestInfo`** parameter in test methods for unique resource naming
- Use **`givenBucket(testInfo)`** for bucket creation — don't create your own helper
- Use **Arrange-Act-Assert** pattern consistently
- Verify HTTP codes, headers (ETag, Content-Type), XML/JSON bodies, and error responses
- DON'T mock AWS SDK clients — use actual SDK clients against S3Mock

## Test Pattern

```kotlin
internal class MyFeatureIT : S3TestBase() {
@Test
fun `should perform operation`(testInfo: TestInfo) {
// Arrange
val bucketName = givenBucket(testInfo)

// Act
s3Client.putObject(
PutObjectRequest.builder().bucket(bucketName).key("key").build(),
RequestBody.fromString("content")
)

// Assert
val response = s3Client.getObject(
GetObjectRequest.builder().bucket(bucketName).key("key").build()
)
assertThat(response.readAllBytes().decodeToString()).isEqualTo("content")
}
}
```

## Common Patterns

**Multipart**: Initiate → Upload parts → Complete → Verify

**Errors** (use AssertJ, not JUnit `assertThrows`):
```kotlin
assertThatThrownBy { s3Client.deleteBucket { it.bucket(bucketName) } }
.isInstanceOf(AwsServiceException::class.java)
.hasMessageContaining("Status Code: 409")
```

## Running

```bash
Expand Down
18 changes: 1 addition & 17 deletions server/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,7 @@ server/src/main/kotlin/com/adobe/testing/s3mock/

## Testing

Service and store unit tests use `@SpringBootTest` with `@MockitoBean`, while controller tests are slice tests using `@WebMvcTest` with `@MockitoBean` and `BaseControllerTest`. Extend the appropriate base class (`ServiceTestBase`, `StoreTestBase`, `BaseControllerTest`):

```kotlin
@SpringBootTest(classes = [ServiceConfiguration::class], webEnvironment = SpringBootTest.WebEnvironment.NONE)
@MockitoBean(types = [BucketService::class, MultipartService::class, MultipartStore::class])
internal class ObjectServiceTest : ServiceTestBase() {
@Autowired
private lateinit var iut: ObjectService

@Test
fun `should get object`() {
whenever(bucketStore.getBucketMetadata("bucket")).thenReturn(bucket)
whenever(objectStore.getObject(bucket, "key")).thenReturn(s3Object)
assertThat(iut.getObject("bucket", "key")).isEqualTo(s3Object)
}
}
```
See **[docs/TESTING.md](../docs/TESTING.md)** for the full strategy. Service and store tests use `@SpringBootTest` with `@MockitoBean`; controller tests use `@WebMvcTest` with `@MockitoBean` and `BaseControllerTest`. Always extend the appropriate base class (`ServiceTestBase`, `StoreTestBase`, `BaseControllerTest`).

## Configuration

Expand Down
2 changes: 1 addition & 1 deletion testsupport/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> Inherits all conventions from the [root AGENTS.md](../AGENTS.md). Below are module-specific additions only.

Test framework integrations for using S3Mock: JUnit 5, Testcontainers, TestNG.
Test framework integrations for using S3Mock: JUnit 5, Testcontainers, TestNG. See **[docs/TESTING.md](../docs/TESTING.md)** for the overall testing strategy.

> **Deprecation Notice (6.x):** The JUnit 5, TestNG, and all direct-integration modules will be
> removed in S3Mock 6.x. Testcontainers will become the only officially supported testing approach.
Expand Down
Loading