refactor: agents and skills move to a client-neutral source that renders per client
`.claude/agents/` was the source of truth, which made every role Claude-Code shaped. Adding a second client meant rewriting each role in that client's syntax and maintaining both copies — the drift this scaffold exists to prevent, one layer up. Roles and skills now live under `.agents/` and render into each registered client. `.claude/agents/`, `.claude/skills/` and `.codex/agents/` are generated; `scripts/sync-agent-integrations.py --check` fails on drift and belongs in CI. The role metadata is portable rather than vendor-named: `reasoning_tier` (deep/balanced/fast/vision), `capabilities`, `mutation`, `invocation`, and an optional `preload_skills`. A client manifest maps those to native syntax and must declare what it cannot express — `codex.yaml` declares `tier_policy: unsupported` and its adapters say so in the file, rather than the tier silently evaporating and leaving the repository to believe it was enforced. The port is behaviour-preserving where it should be and a fix where it should not. Every instruction body is byte-identical — the whole diff to `.claude/agents/` is 18 added lines and zero deletions. What changed is frontmatter that was missing: - four agents (`code-reviewer`, `tdd-guardian`, `dependency-audit`, `pr-creator`) declared no `tools:` and therefore inherited the ENTIRE tool pool, so three review-only agents could edit and write the code they were reviewing. All eight now declare capabilities explicitly. - the six read-only roles gain a non-editing permission mode, so the constraint is enforced by the client rather than by the prompt asking nicely. - `mutation` is now explicit, which records the two roles that genuinely need to write: `pr-creator` (external-write — it pushes a branch and opens a PR) and `dependency-audit` (workspace-write — package managers rewrite lockfiles). `pr-creator` keeps `shell` because opening a PR needs it, but it is now the only agent here with a write mutation and a declared reason for it, instead of one of four with unlimited access by omission.
This commit is contained in:
@@ -0,0 +1,675 @@
|
||||
---
|
||||
name: java-development
|
||||
description: Java development patterns with Spring Boot, JUnit 5, and modern Java practices. Use when writing Java code.
|
||||
---
|
||||
|
||||
# Java Development Skill
|
||||
|
||||
## Project Structure (Spring Boot)
|
||||
|
||||
```
|
||||
project/
|
||||
├── src/
|
||||
│ ├── main/
|
||||
│ │ ├── java/
|
||||
│ │ │ └── com/mycompany/myapp/
|
||||
│ │ │ ├── MyApplication.java
|
||||
│ │ │ ├── domain/
|
||||
│ │ │ │ ├── User.java
|
||||
│ │ │ │ └── Order.java
|
||||
│ │ │ ├── repository/
|
||||
│ │ │ │ └── UserRepository.java
|
||||
│ │ │ ├── service/
|
||||
│ │ │ │ └── UserService.java
|
||||
│ │ │ ├── controller/
|
||||
│ │ │ │ └── UserController.java
|
||||
│ │ │ └── config/
|
||||
│ │ │ └── SecurityConfig.java
|
||||
│ │ └── resources/
|
||||
│ │ ├── application.yml
|
||||
│ │ └── application-test.yml
|
||||
│ └── test/
|
||||
│ └── java/
|
||||
│ └── com/mycompany/myapp/
|
||||
│ ├── service/
|
||||
│ │ └── UserServiceTest.java
|
||||
│ └── controller/
|
||||
│ └── UserControllerTest.java
|
||||
├── pom.xml
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Testing with JUnit 5
|
||||
|
||||
### Unit Tests with Mockito
|
||||
```java
|
||||
package com.mycompany.myapp.service;
|
||||
|
||||
import com.mycompany.myapp.domain.User;
|
||||
import com.mycompany.myapp.repository.UserRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserServiceTest {
|
||||
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
|
||||
@InjectMocks
|
||||
private UserService userService;
|
||||
|
||||
@Nested
|
||||
@DisplayName("createUser")
|
||||
class CreateUser {
|
||||
|
||||
@Test
|
||||
@DisplayName("should create user with valid data")
|
||||
void shouldCreateUserWithValidData() {
|
||||
// Given
|
||||
var request = UserFixtures.createUserRequest();
|
||||
var savedUser = UserFixtures.user();
|
||||
when(userRepository.save(any(User.class))).thenReturn(savedUser);
|
||||
|
||||
// When
|
||||
var result = userService.createUser(request);
|
||||
|
||||
// Then
|
||||
assertThat(result.getId()).isNotNull();
|
||||
assertThat(result.getEmail()).isEqualTo(request.getEmail());
|
||||
verify(userRepository).save(any(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should throw exception for duplicate email")
|
||||
void shouldThrowExceptionForDuplicateEmail() {
|
||||
// Given
|
||||
var request = UserFixtures.createUserRequest();
|
||||
when(userRepository.existsByEmail(request.getEmail())).thenReturn(true);
|
||||
|
||||
// When/Then
|
||||
assertThatThrownBy(() -> userService.createUser(request))
|
||||
.isInstanceOf(DuplicateEmailException.class)
|
||||
.hasMessageContaining(request.getEmail());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("getUserById")
|
||||
class GetUserById {
|
||||
|
||||
@Test
|
||||
@DisplayName("should return user when found")
|
||||
void shouldReturnUserWhenFound() {
|
||||
// Given
|
||||
var user = UserFixtures.user();
|
||||
when(userRepository.findById(user.getId())).thenReturn(Optional.of(user));
|
||||
|
||||
// When
|
||||
var result = userService.getUserById(user.getId());
|
||||
|
||||
// Then
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.get().getId()).isEqualTo(user.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should return empty when user not found")
|
||||
void shouldReturnEmptyWhenNotFound() {
|
||||
// Given
|
||||
when(userRepository.findById(any())).thenReturn(Optional.empty());
|
||||
|
||||
// When
|
||||
var result = userService.getUserById("unknown-id");
|
||||
|
||||
// Then
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Fixtures
|
||||
```java
|
||||
package com.mycompany.myapp.fixtures;
|
||||
|
||||
import com.mycompany.myapp.domain.User;
|
||||
import com.mycompany.myapp.dto.CreateUserRequest;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class UserFixtures {
|
||||
|
||||
private UserFixtures() {}
|
||||
|
||||
public static User user() {
|
||||
return user(builder -> {});
|
||||
}
|
||||
|
||||
public static User user(UserCustomizer customizer) {
|
||||
var builder = User.builder()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.email("[email protected]")
|
||||
.name("Test User")
|
||||
.role(Role.USER)
|
||||
.createdAt(Instant.now())
|
||||
.updatedAt(Instant.now());
|
||||
|
||||
customizer.customize(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static CreateUserRequest createUserRequest() {
|
||||
return createUserRequest(builder -> {});
|
||||
}
|
||||
|
||||
public static CreateUserRequest createUserRequest(CreateUserRequestCustomizer customizer) {
|
||||
var builder = CreateUserRequest.builder()
|
||||
.email("[email protected]")
|
||||
.name("New User")
|
||||
.password("SecurePass123!");
|
||||
|
||||
customizer.customize(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface UserCustomizer {
|
||||
void customize(User.UserBuilder builder);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CreateUserRequestCustomizer {
|
||||
void customize(CreateUserRequest.CreateUserRequestBuilder builder);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage in tests
|
||||
var adminUser = UserFixtures.user(builder -> builder.role(Role.ADMIN));
|
||||
var customRequest = UserFixtures.createUserRequest(builder ->
|
||||
builder.email("[email protected]"));
|
||||
```
|
||||
|
||||
### Parameterized Tests
|
||||
```java
|
||||
package com.mycompany.myapp.validation;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.NullAndEmptySource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
class EmailValidatorTest {
|
||||
|
||||
private final EmailValidator validator = new EmailValidator();
|
||||
|
||||
@ParameterizedTest
|
||||
@DisplayName("should accept valid emails")
|
||||
@ValueSource(strings = {
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
})
|
||||
void shouldAcceptValidEmails(String email) {
|
||||
assertThat(validator.isValid(email)).isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@DisplayName("should reject invalid emails")
|
||||
@ValueSource(strings = {
|
||||
"invalid",
|
||||
"missing@domain",
|
||||
"@nodomain.com",
|
||||
"spaces [email protected]"
|
||||
})
|
||||
void shouldRejectInvalidEmails(String email) {
|
||||
assertThat(validator.isValid(email)).isFalse();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@DisplayName("should reject null and empty emails")
|
||||
@NullAndEmptySource
|
||||
void shouldRejectNullAndEmpty(String email) {
|
||||
assertThat(validator.isValid(email)).isFalse();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@DisplayName("should validate password strength")
|
||||
@CsvSource({
|
||||
"password,false",
|
||||
"Password1,false",
|
||||
"Password1!,true",
|
||||
"P@ssw0rd,true"
|
||||
})
|
||||
void shouldValidatePasswordStrength(String password, boolean expected) {
|
||||
assertThat(validator.isStrongPassword(password)).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Tests with Spring
|
||||
```java
|
||||
package com.mycompany.myapp.controller;
|
||||
|
||||
import com.mycompany.myapp.fixtures.UserFixtures;
|
||||
import com.mycompany.myapp.repository.UserRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
class UserControllerIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
userRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("POST /api/users creates user and returns 201")
|
||||
void createUserReturnsCreated() throws Exception {
|
||||
var request = """
|
||||
{
|
||||
"email": "[email protected]",
|
||||
"name": "New User",
|
||||
"password": "SecurePass123!"
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post("/api/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(request))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.id").isNotEmpty())
|
||||
.andExpect(jsonPath("$.email").value("[email protected]"))
|
||||
.andExpect(jsonPath("$.name").value("New User"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET /api/users/{id} returns user when found")
|
||||
void getUserReturnsUserWhenFound() throws Exception {
|
||||
var user = userRepository.save(UserFixtures.user());
|
||||
|
||||
mockMvc.perform(get("/api/users/{id}", user.getId()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.id").value(user.getId()))
|
||||
.andExpect(jsonPath("$.email").value(user.getEmail()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET /api/users/{id} returns 404 when not found")
|
||||
void getUserReturns404WhenNotFound() throws Exception {
|
||||
mockMvc.perform(get("/api/users/{id}", "unknown-id"))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Domain Models with Records
|
||||
|
||||
```java
|
||||
package com.mycompany.myapp.domain;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.time.Instant;
|
||||
|
||||
// Immutable domain model using records (Java 17+)
|
||||
public record User(
|
||||
String id,
|
||||
@NotBlank @Email String email,
|
||||
@NotBlank @Size(max = 100) String name,
|
||||
Role role,
|
||||
Instant createdAt,
|
||||
Instant updatedAt
|
||||
) {
|
||||
// Compact constructor for validation
|
||||
public User {
|
||||
if (email != null) {
|
||||
email = email.toLowerCase().trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Static factory methods
|
||||
public static User create(String email, String name) {
|
||||
var now = Instant.now();
|
||||
return new User(
|
||||
UUID.randomUUID().toString(),
|
||||
email,
|
||||
name,
|
||||
Role.USER,
|
||||
now,
|
||||
now
|
||||
);
|
||||
}
|
||||
|
||||
// Wither methods for immutable updates
|
||||
public User withName(String newName) {
|
||||
return new User(id, email, newName, role, createdAt, Instant.now());
|
||||
}
|
||||
|
||||
public User withRole(Role newRole) {
|
||||
return new User(id, email, name, newRole, createdAt, Instant.now());
|
||||
}
|
||||
}
|
||||
|
||||
// For mutable entities (JPA)
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
private String email;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Role role;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private Instant createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private Instant updatedAt;
|
||||
|
||||
@PrePersist
|
||||
void onCreate() {
|
||||
createdAt = Instant.now();
|
||||
updatedAt = createdAt;
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
void onUpdate() {
|
||||
updatedAt = Instant.now();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Service Layer
|
||||
|
||||
```java
|
||||
package com.mycompany.myapp.service;
|
||||
|
||||
import com.mycompany.myapp.domain.User;
|
||||
import com.mycompany.myapp.dto.CreateUserRequest;
|
||||
import com.mycompany.myapp.exception.DuplicateEmailException;
|
||||
import com.mycompany.myapp.exception.UserNotFoundException;
|
||||
import com.mycompany.myapp.repository.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class UserService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Transactional
|
||||
public User createUser(CreateUserRequest request) {
|
||||
if (userRepository.existsByEmail(request.getEmail())) {
|
||||
throw new DuplicateEmailException(request.getEmail());
|
||||
}
|
||||
|
||||
var user = User.create(
|
||||
request.getEmail(),
|
||||
request.getName()
|
||||
);
|
||||
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
public Optional<User> getUserById(String id) {
|
||||
return userRepository.findById(id);
|
||||
}
|
||||
|
||||
public User getUserByIdOrThrow(String id) {
|
||||
return userRepository.findById(id)
|
||||
.orElseThrow(() -> new UserNotFoundException(id));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public User updateUser(String id, UpdateUserRequest request) {
|
||||
var user = getUserByIdOrThrow(id);
|
||||
|
||||
var updatedUser = user
|
||||
.withName(request.getName())
|
||||
.withRole(request.getRole());
|
||||
|
||||
return userRepository.save(updatedUser);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteUser(String id) {
|
||||
if (!userRepository.existsById(id)) {
|
||||
throw new UserNotFoundException(id);
|
||||
}
|
||||
userRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## REST Controllers
|
||||
|
||||
```java
|
||||
package com.mycompany.myapp.controller;
|
||||
|
||||
import com.mycompany.myapp.dto.*;
|
||||
import com.mycompany.myapp.service.UserService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
@GetMapping
|
||||
public List<UserResponse> listUsers() {
|
||||
return userService.getAllUsers().stream()
|
||||
.map(userMapper::toResponse)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<UserResponse> getUser(@PathVariable String id) {
|
||||
return userService.getUserById(id)
|
||||
.map(userMapper::toResponse)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public UserResponse createUser(@Valid @RequestBody CreateUserRequest request) {
|
||||
var user = userService.createUser(request);
|
||||
return userMapper.toResponse(user);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public UserResponse updateUser(
|
||||
@PathVariable String id,
|
||||
@Valid @RequestBody UpdateUserRequest request) {
|
||||
var user = userService.updateUser(id, request);
|
||||
return userMapper.toResponse(user);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
public void deleteUser(@PathVariable String id) {
|
||||
userService.deleteUser(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Exception Handling
|
||||
|
||||
```java
|
||||
package com.mycompany.myapp.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(UserNotFoundException.class)
|
||||
public ProblemDetail handleUserNotFound(UserNotFoundException ex) {
|
||||
var problem = ProblemDetail.forStatusAndDetail(
|
||||
HttpStatus.NOT_FOUND,
|
||||
ex.getMessage()
|
||||
);
|
||||
problem.setTitle("User Not Found");
|
||||
problem.setType(URI.create("https://api.myapp.com/errors/user-not-found"));
|
||||
return problem;
|
||||
}
|
||||
|
||||
@ExceptionHandler(DuplicateEmailException.class)
|
||||
public ProblemDetail handleDuplicateEmail(DuplicateEmailException ex) {
|
||||
var problem = ProblemDetail.forStatusAndDetail(
|
||||
HttpStatus.CONFLICT,
|
||||
ex.getMessage()
|
||||
);
|
||||
problem.setTitle("Duplicate Email");
|
||||
problem.setType(URI.create("https://api.myapp.com/errors/duplicate-email"));
|
||||
return problem;
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
|
||||
var problem = ProblemDetail.forStatusAndDetail(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Validation failed"
|
||||
);
|
||||
problem.setTitle("Validation Error");
|
||||
|
||||
var errors = ex.getBindingResult().getFieldErrors().stream()
|
||||
.map(e -> new ValidationError(e.getField(), e.getDefaultMessage()))
|
||||
.toList();
|
||||
|
||||
problem.setProperty("errors", errors);
|
||||
return problem;
|
||||
}
|
||||
|
||||
record ValidationError(String field, String message) {}
|
||||
}
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Maven
|
||||
mvn clean install # Build and test
|
||||
mvn test # Run tests only
|
||||
mvn verify # Run integration tests
|
||||
mvn spring-boot:run # Run application
|
||||
mvn jacoco:report # Generate coverage report
|
||||
|
||||
# Gradle
|
||||
./gradlew build # Build and test
|
||||
./gradlew test # Run tests only
|
||||
./gradlew integrationTest # Run integration tests
|
||||
./gradlew bootRun # Run application
|
||||
./gradlew jacocoTestReport # Generate coverage report
|
||||
|
||||
# Common flags
|
||||
-DskipTests # Skip tests
|
||||
-Dspring.profiles.active=test # Set profile
|
||||
```
|
||||
|
||||
## Dependencies (pom.xml)
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<!-- Spring Boot -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
Reference in New Issue
Block a user