diff --git a/src/main/java/it/cnr/isti/workflow/manager/ServletInitializer.java b/src/main/java/it/cnr/isti/workflow/manager/ServletInitializer.java index 1999218..c93ff46 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/ServletInitializer.java +++ b/src/main/java/it/cnr/isti/workflow/manager/ServletInitializer.java @@ -3,6 +3,8 @@ package it.cnr.isti.workflow.manager; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import it.cnr.isti.workflow.manager.WorkflowManagerApplication; + public class ServletInitializer extends SpringBootServletInitializer { @Override diff --git a/src/main/java/it/cnr/isti/workflow/manager/WorkflowManagerApplication.java b/src/main/java/it/cnr/isti/workflow/manager/WorkflowManagerApplication.java index 7e0daae..8214b8e 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/WorkflowManagerApplication.java +++ b/src/main/java/it/cnr/isti/workflow/manager/WorkflowManagerApplication.java @@ -1,14 +1,19 @@ package it.cnr.isti.workflow.manager; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; +import it.cnr.isti.workflow.manager.auth.services.UserImportComponent; + @SpringBootApplication public class WorkflowManagerApplication { + @Autowired + UserImportComponent userImportComponent; public static void main(String[] args) { SpringApplication.run(WorkflowManagerApplication.class, args); @@ -18,7 +23,8 @@ public class WorkflowManagerApplication { @ConditionalOnProperty(prefix = "app", name = "db.init.enabled", havingValue = "true") CommandLineRunner init() { return args -> { - + System.out.println("Initializing database..."); + userImportComponent.start(); }; } diff --git a/src/main/java/it/cnr/isti/workflow/manager/app/ObjectMapperHolder.java b/src/main/java/it/cnr/isti/workflow/manager/app/ObjectMapperHolder.java new file mode 100644 index 0000000..c612363 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/app/ObjectMapperHolder.java @@ -0,0 +1,14 @@ +package it.cnr.isti.workflow.manager.app; + +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Component +public class ObjectMapperHolder { + public static ObjectMapper mapper; + + public ObjectMapperHolder(ObjectMapper objectMapper) { + ObjectMapperHolder.mapper = objectMapper; + } +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/Validator.java b/src/main/java/it/cnr/isti/workflow/manager/app/Validator.java similarity index 62% rename from src/main/java/it/cnr/isti/workflow/manager/Validator.java rename to src/main/java/it/cnr/isti/workflow/manager/app/Validator.java index f21715b..b1708c6 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/Validator.java +++ b/src/main/java/it/cnr/isti/workflow/manager/app/Validator.java @@ -1,4 +1,4 @@ -package it.cnr.isti.workflow.manager; +package it.cnr.isti.workflow.manager.app; public interface Validator { diff --git a/src/main/java/it/cnr/isti/workflow/manager/app/config/OpenApiConfig.java b/src/main/java/it/cnr/isti/workflow/manager/app/config/OpenApiConfig.java new file mode 100644 index 0000000..700b9ac --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/app/config/OpenApiConfig.java @@ -0,0 +1,30 @@ +package it.cnr.isti.workflow.manager.app.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.security.SecurityScheme.Type; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenApiConfig { + + @Bean + OpenAPI customOpenAPI() { + final String securitySchemeName = "bearerAuth"; + + return new OpenAPI() + .info(new Info().title("API Docs").version("v1")) + .components(new io.swagger.v3.oas.models.Components() + .addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .name(securitySchemeName) + .type(Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } +} + diff --git a/src/main/java/it/cnr/isti/workflow/manager/app/config/WebConfig.java b/src/main/java/it/cnr/isti/workflow/manager/app/config/WebConfig.java new file mode 100644 index 0000000..de555b0 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/app/config/WebConfig.java @@ -0,0 +1,35 @@ +package it.cnr.isti.workflow.manager.app.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.NonNull; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import jakarta.validation.Validator; + +import org.springframework.context.annotation.Bean; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Value("${cors.allowed-origins}") + private String allowedOrigins; + + @Override + public void addCorsMappings(@NonNull CorsRegistry registry) { + String[] origins = allowedOrigins.split(","); + + registry.addMapping("/**") + .allowedOrigins(origins) + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*"); + } + + @Bean + public Validator validator() { + return new LocalValidatorFactoryBean(); + } + +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtAuthenticationFilter.java b/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtAuthenticationFilter.java new file mode 100644 index 0000000..5f02ee7 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtAuthenticationFilter.java @@ -0,0 +1,66 @@ +package it.cnr.isti.workflow.manager.auth.config; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import it.cnr.isti.workflow.manager.auth.repo.AuthRepository; +import it.cnr.isti.workflow.manager.auth.repo.LoginEntity; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private AuthRepository authRepository; + + private static final List EXCLUDED_PATHS = List.of("/auth/", "/health"); + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws ServletException, IOException { + + String path = request.getRequestURI(); + + if (EXCLUDED_PATHS.stream().anyMatch(path::startsWith)) { + filterChain.doFilter(request, response); + return; + } + + String authHeader = request.getHeader("Authorization"); + + if (authHeader != null && authHeader.startsWith("Bearer ")) { + String jwt = authHeader.substring(7); + String username = jwtUtil.extractUsername(jwt); + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + LoginEntity userDetails = authRepository.findById(username).orElse(null); + + if (jwtUtil.validateToken(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, + null, Collections.emptyList()); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtUtil.java b/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtUtil.java new file mode 100644 index 0000000..d22faf6 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtUtil.java @@ -0,0 +1,58 @@ +package it.cnr.isti.workflow.manager.auth.config; + + +import java.util.Date; + +import javax.crypto.SecretKey; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import it.cnr.isti.workflow.manager.auth.repo.LoginEntity; + +@Component +public class JwtUtil { + + SecretKey secretKey; + + public JwtUtil( @Value("${app.security.key}") String secretKeyString) { + this.secretKey = Keys.hmacShaKeyFor(secretKeyString.getBytes()); + } + + public String generateToken(String username) { + String jwt = Jwts.builder() + .setSubject(username) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1 giorno + .signWith(secretKey, SignatureAlgorithm.HS256) // Usa una SecretKey (HMAC) + .compact(); + return jwt; + } + + public String extractUsername(String token) { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject(); + } + + public boolean validateToken(String token, LoginEntity userDetails) { + final String username = extractUsername(token); + return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } + + private boolean isTokenExpired(String token) { + Date expiration = Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody() + .getExpiration(); + return expiration.before(new Date()); + } +} \ No newline at end of file diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/config/SecurityConfig.java b/src/main/java/it/cnr/isti/workflow/manager/auth/config/SecurityConfig.java new file mode 100644 index 0000000..8df5b93 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/config/SecurityConfig.java @@ -0,0 +1,46 @@ +package it.cnr.isti.workflow.manager.auth.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class SecurityConfig { + + @Autowired + private JwtAuthenticationFilter jwtFilter; + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .cors(Customizer.withDefaults()) + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/auth/**").permitAll() + .requestMatchers("/swagger-ui.html").permitAll() + .requestMatchers("/swagger-ui/**").permitAll() + .requestMatchers("/v3/api-docs/**").permitAll() + .anyRequest().authenticated()) + .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .build(); + } + + @Bean + AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } +} \ No newline at end of file diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/model/AuthRequest.java b/src/main/java/it/cnr/isti/workflow/manager/auth/model/AuthRequest.java new file mode 100644 index 0000000..fec60ff --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/model/AuthRequest.java @@ -0,0 +1,24 @@ +package it.cnr.isti.workflow.manager.auth.model; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +public class AuthRequest { + private String username; + private String password; + + // Getters e Setters + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + + boolean isValid() { + return username.contains(" ") && username != null && !username.isEmpty() && username.length()>5 && + password != null && username.contains(" ") && password.length()>5 && !password.isEmpty(); + } +} + diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/model/ChangePasswordRequest.java b/src/main/java/it/cnr/isti/workflow/manager/auth/model/ChangePasswordRequest.java new file mode 100644 index 0000000..e78e298 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/model/ChangePasswordRequest.java @@ -0,0 +1,18 @@ +package it.cnr.isti.workflow.manager.auth.model; + + +public class ChangePasswordRequest { + private String username; + private String oldPassword; + private String newPassword; + + // Getters e Setters + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getOldPassword() { return oldPassword; } + public void setOldPassword(String oldPassword) { this.oldPassword = oldPassword; } + + public String getNewPassword() { return newPassword; } + public void setNewPassword(String newPassword) { this.newPassword = newPassword; } +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/repo/AuthRepository.java b/src/main/java/it/cnr/isti/workflow/manager/auth/repo/AuthRepository.java new file mode 100644 index 0000000..e2b07bf --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/repo/AuthRepository.java @@ -0,0 +1,11 @@ +package it.cnr.isti.workflow.manager.auth.repo; + + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + + +@Repository +public interface AuthRepository extends JpaRepository { + +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/repo/LoginEntity.java b/src/main/java/it/cnr/isti/workflow/manager/auth/repo/LoginEntity.java new file mode 100644 index 0000000..8754ad2 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/repo/LoginEntity.java @@ -0,0 +1,17 @@ +package it.cnr.isti.workflow.manager.auth.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginEntity { + @Id + private String username; + private String password; +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/services/AuthService.java b/src/main/java/it/cnr/isti/workflow/manager/auth/services/AuthService.java new file mode 100644 index 0000000..5a47fa0 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/services/AuthService.java @@ -0,0 +1,50 @@ +package it.cnr.isti.workflow.manager.auth.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import it.cnr.isti.workflow.manager.auth.repo.AuthRepository; +import it.cnr.isti.workflow.manager.auth.repo.LoginEntity; + +import static it.cnr.isti.workflow.manager.auth.services.PasswordHasher.*; + +@Service +public class AuthService { + + + @Autowired + AuthRepository authRepository; + + public boolean validateUser(String username, String password) throws UsernameNotFoundException { + LoginEntity le = authRepository.findById(username).orElse(null); + if (le == null) { + throw new UsernameNotFoundException("User not found"); + } + return checkPassword(password, le.getPassword()); // Validate password + + } + + public boolean registerUser(String username, String password) { + if (authRepository.existsById(username)) { + return false; // User already exists + } + String hashedPassword = hashPassword(password); + authRepository.save(new LoginEntity(username, hashedPassword)); + return true; + } + + public boolean changePassword(String username, String oldPassword, String newPassword) { + return authRepository.findById(username) + .filter(user -> checkPassword(oldPassword, user.getPassword())) + .map(user -> { + user.setPassword(hashPassword(newPassword)); + authRepository.save(user); + return true; + }) + .orElse(false); + } + + + +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/services/PasswordHasher.java b/src/main/java/it/cnr/isti/workflow/manager/auth/services/PasswordHasher.java new file mode 100644 index 0000000..2c32076 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/services/PasswordHasher.java @@ -0,0 +1,16 @@ +package it.cnr.isti.workflow.manager.auth.services; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public class PasswordHasher { + + private static final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + public static String hashPassword(String plainPassword) { + return passwordEncoder.encode(plainPassword); + } + + public static boolean checkPassword(String plainPassword, String hashedPassword) { + return passwordEncoder.matches(plainPassword, hashedPassword); + } +} \ No newline at end of file diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/services/UserImportComponent.java b/src/main/java/it/cnr/isti/workflow/manager/auth/services/UserImportComponent.java new file mode 100644 index 0000000..af5ec28 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/auth/services/UserImportComponent.java @@ -0,0 +1,69 @@ +package it.cnr.isti.workflow.manager.auth.services; + +import java.io.File; +import java.util.List; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import it.cnr.isti.workflow.manager.app.ObjectMapperHolder; +import it.cnr.isti.workflow.manager.auth.repo.AuthRepository; +import it.cnr.isti.workflow.manager.auth.repo.LoginEntity; + +@Component +public class UserImportComponent { + + private static final Logger logger = org.slf4j.LoggerFactory.getLogger(UserImportComponent.class); + + private static final String USERS_FILE_NAME = "users.json"; + + @Autowired + AuthRepository authRepository; + + @Value("${app.import.enabled}") + boolean enabled; + + @Value("${app.import.path}") + String path; + + public void start() { + if (!enabled) { + logger.warn("ImportComponent is disabled"); + return; + } + + File usersFile = new File(path, USERS_FILE_NAME); + + if (usersFile.exists()) + insertUser(usersFile); + else + logger.warn("Users file does not exist: {}", usersFile.getAbsolutePath()); + } + + private void insertUser(File file) { + try { + List users = ObjectMapperHolder.mapper.readValue(file, + ObjectMapperHolder.mapper.getTypeFactory().constructCollectionType(List.class, LoginEntity.class)); + for (LoginEntity user : users) { + authRepository.findById(user.getUsername()).ifPresentOrElse( + existingUser -> { + logger.debug("User {} already present in the database, updating password", + user.getUsername()); + existingUser.setPassword(PasswordHasher.hashPassword(user.getPassword())); + authRepository.save(existingUser); + }, + () -> { + logger.debug("User {} not found, saving new user", user.getUsername()); + user.setPassword(PasswordHasher.hashPassword(user.getPassword())); + authRepository.save(user); + }); + + } + } catch (Exception e) { + logger.error("Error loading users: {}", e.getMessage(), e); + } + } + +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/blocks/types/BlockType.java b/src/main/java/it/cnr/isti/workflow/manager/blocks/types/BlockType.java index d5fdf11..57a9c01 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/blocks/types/BlockType.java +++ b/src/main/java/it/cnr/isti/workflow/manager/blocks/types/BlockType.java @@ -1,5 +1,9 @@ package it.cnr.isti.workflow.manager.blocks.types; + + +import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration; + public interface BlockType { String getName(); @@ -8,5 +12,5 @@ public interface BlockType { boolean validate(); - String getBlockConfigurationSchema(); + Class> getBlockConfigurationClass(); } diff --git a/src/main/java/it/cnr/isti/workflow/manager/blocks/types/HumanInteractionBlockType.java b/src/main/java/it/cnr/isti/workflow/manager/blocks/types/HumanInteractionBlockType.java index 95eda82..ac6816b 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/blocks/types/HumanInteractionBlockType.java +++ b/src/main/java/it/cnr/isti/workflow/manager/blocks/types/HumanInteractionBlockType.java @@ -2,6 +2,8 @@ package it.cnr.isti.workflow.manager.blocks.types; import org.springframework.stereotype.Component; +import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration; + @Component(HumanInteractionBlockType.TYPE) public class HumanInteractionBlockType implements BlockType { @@ -23,7 +25,7 @@ public class HumanInteractionBlockType implements BlockType { } @Override - public String getBlockConfigurationSchema() { + public Class> getBlockConfigurationClass() { return null; } diff --git a/src/main/java/it/cnr/isti/workflow/manager/blocks/types/LLMBlockType.java b/src/main/java/it/cnr/isti/workflow/manager/blocks/types/LLMBlockType.java index 82fd5a9..8a16fb9 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/blocks/types/LLMBlockType.java +++ b/src/main/java/it/cnr/isti/workflow/manager/blocks/types/LLMBlockType.java @@ -2,6 +2,9 @@ package it.cnr.isti.workflow.manager.blocks.types; import org.springframework.stereotype.Component; +import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration; +import it.cnr.isti.workflow.manager.blocks.configurations.LLMBlockConfiguration; + @Component(LLMBlockType.TYPE) public class LLMBlockType implements BlockType { @@ -23,8 +26,8 @@ public class LLMBlockType implements BlockType { } @Override - public String getBlockConfigurationSchema() { - return "Inputs: AT_LEAST_ONE, Outputs: SINGLE, Models: SINGLE"; + public Class> getBlockConfigurationClass() { + return LLMBlockConfiguration.class; } } diff --git a/src/main/java/it/cnr/isti/workflow/manager/blocks/types/SourceBlockType.java b/src/main/java/it/cnr/isti/workflow/manager/blocks/types/SourceBlockType.java index c1e3ac0..455961b 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/blocks/types/SourceBlockType.java +++ b/src/main/java/it/cnr/isti/workflow/manager/blocks/types/SourceBlockType.java @@ -2,6 +2,8 @@ package it.cnr.isti.workflow.manager.blocks.types; import org.springframework.stereotype.Component; +import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration; + @Component(SourceBlockType.TYPE) public class SourceBlockType implements BlockType { @@ -23,8 +25,8 @@ public class SourceBlockType implements BlockType { } @Override - public String getBlockConfigurationSchema() { - return null; + public Class> getBlockConfigurationClass() { + return null; // Assuming no specific configuration class for SourceBlockType } } diff --git a/src/main/java/it/cnr/isti/workflow/manager/bricks/Brick.java b/src/main/java/it/cnr/isti/workflow/manager/bricks/Brick.java index 68313f2..7c12294 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/bricks/Brick.java +++ b/src/main/java/it/cnr/isti/workflow/manager/bricks/Brick.java @@ -10,15 +10,15 @@ import lombok.NoArgsConstructor; public abstract class Brick { private String id; - private String brickProvider; + private String brickManager; - protected Brick(String id, String brickProvider) { - this.brickProvider = brickProvider; - this.id = this.brickProvider + ":" + id; + public Brick(String id, String brickManager) { + this.brickManager = brickManager; + this.id = this.brickManager + ":" + id; } - public String getBrickProvider() { - return brickProvider; + public String getBrickManager() { + return brickManager; } public Map getExecutionTimeConfiguration() { diff --git a/src/main/java/it/cnr/isti/workflow/manager/bricks/BrickManager.java b/src/main/java/it/cnr/isti/workflow/manager/bricks/BrickManager.java index 25fd50f..09b06bd 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/bricks/BrickManager.java +++ b/src/main/java/it/cnr/isti/workflow/manager/bricks/BrickManager.java @@ -8,8 +8,6 @@ import lombok.NoArgsConstructor; @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public abstract class BrickManager { - public abstract List possibleValues(String key); - public abstract List getBricks(); } diff --git a/src/main/java/it/cnr/isti/workflow/manager/bricks/LLMBrick.java b/src/main/java/it/cnr/isti/workflow/manager/bricks/LLMBrick.java index 923eba2..2c6dd84 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/bricks/LLMBrick.java +++ b/src/main/java/it/cnr/isti/workflow/manager/bricks/LLMBrick.java @@ -7,8 +7,8 @@ public class LLMBrick extends Brick { private final String model; - public LLMBrick(String provider, String model, String brickProvider) { - super(provider + "_" + model, brickProvider); + public LLMBrick(String provider, String model, String brickManager) { + super(provider + "_" + model, brickManager); this.provider = provider; this.model = model; } diff --git a/src/main/java/it/cnr/isti/workflow/manager/bricks/LLMBrickManager.java b/src/main/java/it/cnr/isti/workflow/manager/bricks/LLMBrickManager.java index 6ced957..0a244b9 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/bricks/LLMBrickManager.java +++ b/src/main/java/it/cnr/isti/workflow/manager/bricks/LLMBrickManager.java @@ -25,10 +25,5 @@ public class LLMBrickManager extends BrickManager { .forEach(brick -> bricks.add(brick))); return bricks; } - - public List possibleValues(String key) { - return List.of("model1", "model2", "model3"); // Example values, replace with actual logic - } - } diff --git a/src/main/java/it/cnr/isti/workflow/manager/bricks/configurations/BrickConfiguration.java b/src/main/java/it/cnr/isti/workflow/manager/bricks/configurations/BrickConfiguration.java deleted file mode 100644 index 2d7abe6..0000000 --- a/src/main/java/it/cnr/isti/workflow/manager/bricks/configurations/BrickConfiguration.java +++ /dev/null @@ -1,5 +0,0 @@ -package it.cnr.isti.workflow.manager.bricks.configurations; - -public interface BrickConfiguration { - -} diff --git a/src/main/java/it/cnr/isti/workflow/manager/bricks/configurations/LLMBrickConfiguration.java b/src/main/java/it/cnr/isti/workflow/manager/bricks/configurations/LLMBrickConfiguration.java deleted file mode 100644 index 0b973b9..0000000 --- a/src/main/java/it/cnr/isti/workflow/manager/bricks/configurations/LLMBrickConfiguration.java +++ /dev/null @@ -1,18 +0,0 @@ -package it.cnr.isti.workflow.manager.bricks.configurations; - -import it.cnr.isti.workflow.manager.schema.DynamicMultiValues; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -public class LLMBrickConfiguration implements BrickConfiguration { - - @NotBlank - String provider; - - @DynamicMultiValues(key = "models", provider = "llmBrickProvider") - String model; - -} diff --git a/src/main/java/it/cnr/isti/workflow/manager/controllers/AuthController.java b/src/main/java/it/cnr/isti/workflow/manager/controllers/AuthController.java new file mode 100644 index 0000000..d424a1d --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/controllers/AuthController.java @@ -0,0 +1,72 @@ +package it.cnr.isti.workflow.manager.controllers; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import it.cnr.isti.workflow.manager.auth.config.JwtUtil; +import it.cnr.isti.workflow.manager.auth.model.AuthRequest; +import it.cnr.isti.workflow.manager.auth.model.ChangePasswordRequest; +import it.cnr.isti.workflow.manager.auth.services.AuthService; + +import java.util.Collections; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@RestController +@RequestMapping("/auth") +public class AuthController { + + private static final Logger logger = org.slf4j.LoggerFactory.getLogger(AuthController.class); + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private AuthService authService; + + @PostMapping("/login") + public ResponseEntity login(@RequestBody AuthRequest request) { + logger.info("Login attempt for user: {}", request.getUsername()); + if (request.getUsername() == null || request.getPassword() == null) { + return ResponseEntity.badRequest().body("Username and password are required"); + } + try { + if (!authService.validateUser(request.getUsername(), request.getPassword())) { + return ResponseEntity.status(401).body("Invalid password"); + } + String jwt = jwtUtil.generateToken(request.getUsername()); + return ResponseEntity.ok(Collections.singletonMap("token", jwt)); + } catch (UsernameNotFoundException e) { + return ResponseEntity.status(404).body("User "+request.getUsername()+" not found"); + } + } + + @PostMapping("/register") + public ResponseEntity register(@RequestBody AuthRequest request) { + logger.info("Register attempt for user {} with pwd {}", request.getUsername(), request.getPassword()); + if (request.getUsername() == null || request.getPassword() == null) { + return ResponseEntity.badRequest().body("Username and password are required"); + } + if (request.getUsername().length() < 3 || request.getPassword().length() < 6) { + return ResponseEntity.badRequest() + .body("Username must be at least 3 characters and password at least 6 characters"); + } + if (authService.registerUser(request.getUsername(), request.getPassword())) { + return ResponseEntity.ok().build(); + } + return ResponseEntity.badRequest().body("the user already exists"); + } + + @PostMapping("/change-password") + public ResponseEntity changePassword(@RequestBody ChangePasswordRequest request) { + if (authService.changePassword(request.getUsername(), request.getOldPassword(), request.getNewPassword())) { + return ResponseEntity.ok().build(); + } + return ResponseEntity.badRequest().body("Change password failed"); + } +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/controllers/BlocksController.java b/src/main/java/it/cnr/isti/workflow/manager/controllers/BlocksController.java index 4919060..6aa2294 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/controllers/BlocksController.java +++ b/src/main/java/it/cnr/isti/workflow/manager/controllers/BlocksController.java @@ -2,17 +2,16 @@ package it.cnr.isti.workflow.manager.controllers; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; - import it.cnr.isti.workflow.manager.blocks.Block; import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration; import it.cnr.isti.workflow.manager.blocks.factories.BlockFactory; import it.cnr.isti.workflow.manager.blocks.types.BlockType; +import it.cnr.isti.workflow.manager.schema.JsonSchemaProducer; import jakarta.validation.Valid; - import java.util.ArrayList; import java.util.List; import java.util.Map; - +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -29,22 +28,29 @@ public class BlocksController { @Autowired List> blockFactories; - @GetMapping("") + @Autowired + JsonSchemaProducer schemaProducer; + + @GetMapping("types") public List getTypes() { return new ArrayList<>(blockTypes.values()); } - @GetMapping("/{type}/configuration") - public String getConfigurations(@PathVariable String type) { - BlockType blockType = blockTypes.get(type); - if (blockType == null) { - throw new IllegalArgumentException("Block type not found: " + type); - } - return blockType.getBlockConfigurationSchema(); + @GetMapping("/types/configurations/schemas") + public List getSchemaConfigurations() { + return blockTypes.values().stream() + .map(b -> schemaProducer.generateSchema(b.getBlockConfigurationClass())) + .collect(Collectors.toList()); + } + + @GetMapping("/types/{type}/configuration/schemas") + public String getSchemaConfigurationForType(@PathVariable String type) { + return schemaProducer.generateSchema( + blockTypes.get(type).getBlockConfigurationClass()); } @SuppressWarnings("unchecked") - @PostMapping() + @PostMapping public > Block create(@RequestBody @Valid C blockConfiguration) { BlockFactory factory = (BlockFactory) blockFactories.stream() .filter(f -> f.getBlockType().equals(blockConfiguration.getType())) diff --git a/src/main/java/it/cnr/isti/workflow/manager/controllers/BricksController.java b/src/main/java/it/cnr/isti/workflow/manager/controllers/BricksController.java index 2b66a64..2d56099 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/controllers/BricksController.java +++ b/src/main/java/it/cnr/isti/workflow/manager/controllers/BricksController.java @@ -9,8 +9,6 @@ import it.cnr.isti.workflow.manager.bricks.BrickManager; import java.util.List; import java.util.Map; -import javax.validation.constraints.NotBlank; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/src/main/java/it/cnr/isti/workflow/manager/controllers/FlowController.java b/src/main/java/it/cnr/isti/workflow/manager/controllers/FlowController.java new file mode 100644 index 0000000..3aeaa22 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/controllers/FlowController.java @@ -0,0 +1,21 @@ +package it.cnr.isti.workflow.manager.controllers; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import it.cnr.isti.workflow.manager.flows.repo.FlowEntity; + +@RestController +@RequestMapping("/flows") +public class FlowController { + + + @PostMapping + public ResponseEntity createFlow(@RequestBody FlowEntity flowEntity) { + FlowEntity createdFlow = flowService.createFlow(flowEntity); + + } + +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/flows/FlowService.java b/src/main/java/it/cnr/isti/workflow/manager/flows/FlowService.java new file mode 100644 index 0000000..51ee1c4 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/flows/FlowService.java @@ -0,0 +1,29 @@ +package it.cnr.isti.workflow.manager.flows; + +import java.time.LocalDateTime; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import it.cnr.isti.workflow.manager.flows.model.Flow; +import it.cnr.isti.workflow.manager.flows.repo.FlowEntity; +import it.cnr.isti.workflow.manager.flows.repo.FlowRepository; + +@Service +public class FlowService { + + @Autowired + FlowRepository flowRepository; + + public FlowEntity createFlow(String name, String description, String owner, Flow flow) { + FlowEntity flowEntity = new FlowEntity(); + LocalDateTime now = LocalDateTime.now(); + flowEntity.setCreatedAt(now); + flowEntity.setLastUpdateAt(now); + flowEntity.setFlow(flow); + flowEntity.setOwner(owner); + flowEntity.setName(name); + flowEntity.setDescription(description); + return flowRepository.save(flowEntity); + } +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/flows/Connection.java b/src/main/java/it/cnr/isti/workflow/manager/flows/model/Connection.java similarity index 100% rename from src/main/java/it/cnr/isti/workflow/manager/flows/Connection.java rename to src/main/java/it/cnr/isti/workflow/manager/flows/model/Connection.java diff --git a/src/main/java/it/cnr/isti/workflow/manager/flows/Flow.java b/src/main/java/it/cnr/isti/workflow/manager/flows/model/Flow.java similarity index 89% rename from src/main/java/it/cnr/isti/workflow/manager/flows/Flow.java rename to src/main/java/it/cnr/isti/workflow/manager/flows/model/Flow.java index b3ae4b4..4cfe4f1 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/flows/Flow.java +++ b/src/main/java/it/cnr/isti/workflow/manager/flows/model/Flow.java @@ -1,4 +1,4 @@ -package it.cnr.isti.workflow.manager.flows; +package it.cnr.isti.workflow.manager.flows.model; import java.util.List; diff --git a/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowEntity.java b/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowEntity.java new file mode 100644 index 0000000..67f6446 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowEntity.java @@ -0,0 +1,55 @@ +package it.cnr.isti.workflow.manager.flows.repo; + +import java.sql.Date; +import java.time.LocalDateTime; + +import it.cnr.isti.workflow.manager.flows.model.Flow; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Data +@Builder +public class FlowEntity { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private String id; + + @NotBlank + private String name; + + private String description; + + @NotBlank + @Column(nullable = false) + private LocalDateTime createdAt; + + @NotBlank + @Column(nullable = false) + private LocalDateTime lastUpdateAt; + + @NotBlank + private String owner; + + @Builder.Default + private boolean published = false; + + @Builder.Default + private boolean finalized = false; + + @Lob + @Column(name = "flow_data", columnDefinition = "TEXT") + private Flow flow; +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowRepository.java b/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowRepository.java new file mode 100644 index 0000000..e656fea --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/flows/repo/FlowRepository.java @@ -0,0 +1,16 @@ +package it.cnr.isti.workflow.manager.flows.repo; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + + +@Repository +public interface FlowRepository extends JpaRepository { + + @Query("SELECT f FROM FlowEntity f WHERE f.owner = :owner OR f.published = true") + List findFlowsByOwnerOrPublic(@Param("owner") String owner); +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/schema/DynamicMultiValues.java b/src/main/java/it/cnr/isti/workflow/manager/schema/DynamicMultiValues.java index 814f6fb..007b9cd 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/schema/DynamicMultiValues.java +++ b/src/main/java/it/cnr/isti/workflow/manager/schema/DynamicMultiValues.java @@ -18,6 +18,5 @@ public @interface DynamicMultiValues { String message() default "value not valid"; Class[] groups() default {}; Class[] payload() default {}; - String key(); - String provider(); + String[] acceptedValues(); } \ No newline at end of file diff --git a/src/main/java/it/cnr/isti/workflow/manager/schema/DynamicSchemaGenerator.java b/src/main/java/it/cnr/isti/workflow/manager/schema/DynamicSchemaGenerator.java index b98de4c..8b6f954 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/schema/DynamicSchemaGenerator.java +++ b/src/main/java/it/cnr/isti/workflow/manager/schema/DynamicSchemaGenerator.java @@ -21,14 +21,13 @@ public class DynamicSchemaGenerator { private final ObjectMapper objectMapper; private final JsonSchemaGenerator baseGenerator; - private Map> brickManagers; - - public DynamicSchemaGenerator(@Autowired ObjectMapper objectMapper, @Autowired Map> brickManagers) { - this.objectMapper = objectMapper; + + public DynamicSchemaGenerator(@Autowired ObjectMapper objectMapper) { JsonSchemaConfig config = JsonSchemaConfig.vanillaJsonSchemaDraft4() .withFailOnUnknownProperties(false); this.baseGenerator = new JsonSchemaGenerator(objectMapper, config); // bean validation = true - this.brickManagers = brickManagers; + this.objectMapper = objectMapper; + } public JsonNode generateJsonSchema(Class clazz) { @@ -38,15 +37,14 @@ public class DynamicSchemaGenerator { for (Field field : clazz.getDeclaredFields()) { DynamicMultiValues annotation = field.getAnnotation(DynamicMultiValues.class); if (annotation != null) { - String key = annotation.key(); - String provider = annotation.provider(); - List enumValues = brickManagers.get(provider).possibleValues(key); - if (enumValues != null) { + String[] acceptedValues = annotation.acceptedValues(); + + if (acceptedValues != null) { // Naviga nello schema e inietta "enum" JsonNode properties = schema.get("properties"); if (properties != null && properties.has(field.getName())) { ArrayNode enumArray = objectMapper.createArrayNode(); - for (String value : enumValues) { + for (String value : acceptedValues) { enumArray.add(value); } ((com.fasterxml.jackson.databind.node.ObjectNode) properties.get(field.getName())) diff --git a/src/main/java/it/cnr/isti/workflow/manager/schema/JsonSchemaProducer.java b/src/main/java/it/cnr/isti/workflow/manager/schema/JsonSchemaProducer.java index db45ab2..17ca28a 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/schema/JsonSchemaProducer.java +++ b/src/main/java/it/cnr/isti/workflow/manager/schema/JsonSchemaProducer.java @@ -3,6 +3,7 @@ package it.cnr.isti.workflow.manager.schema; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -15,8 +16,12 @@ public class JsonSchemaProducer { @Autowired private ObjectMapper mapper; - public String generateSchema(Class clazz) throws Exception { + public String generateSchema(Class clazz) { JsonNode schema = schemaGen.generateJsonSchema(clazz); - return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema); + try { + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema); + } catch (JsonProcessingException e) { + throw new RuntimeException("Error generating JSON schema for class: " + clazz.getName(), e); + } } } diff --git a/src/main/java/it/cnr/isti/workflow/manager/schema/MultiValuesValidator.java b/src/main/java/it/cnr/isti/workflow/manager/schema/MultiValuesValidator.java index db6f217..330b27d 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/schema/MultiValuesValidator.java +++ b/src/main/java/it/cnr/isti/workflow/manager/schema/MultiValuesValidator.java @@ -14,8 +14,7 @@ import java.util.Map; @Component public class MultiValuesValidator implements ConstraintValidator { - private String key; - private String provider; + private String[] acceptedValues; private Map> brickManagers; @@ -25,13 +24,11 @@ public class MultiValuesValidator implements ConstraintValidator allowed = brickManagers.get(provider).possibleValues(key); - return value == null || allowed.contains(value); + return value == null || List.of(acceptedValues).contains(value); } } diff --git a/src/main/resources/workflow-editor-init/flows.json b/src/main/resources/workflow-editor-init/flows.json deleted file mode 100644 index c22e677..0000000 --- a/src/main/resources/workflow-editor-init/flows.json +++ /dev/null @@ -1,298 +0,0 @@ -[ - { - "createdBy": "user.test", - "name": "testFlow", - "description": null, - "nodes": [ - { - "key": "e5f1f8d6-7717-4e32-ab57-f25f38f55401", - "name": "newNode", - "createdBy": null, - "outputs": [ - { - "key": "2967ca92-3977-44ad-8e19-318b9a71d227", - "type": "text", - "name": "value" - } - ], - "inputs": [], - "color": "#A8E6CF", - "position": { - "x": -270, - "y": -16 - }, - "parameters": { - "name": "interview\n" - }, - "description": null, - "type": "Text Input" - }, - { - "key": "1a4d1254-2c44-49fa-8aee-3865d1be9f95", - "name": "newNode", - "createdBy": null, - "outputs": [ - { - "key": "f36c7ae1-78da-4d71-8b44-58a1f87f17cb", - "type": "text", - "name": "requirements" - } - ], - "inputs": [ - { - "key": "7860945a-506b-45a3-97b6-cbf08023e421", - "type": "text", - "name": "interview" - } - ], - "color": "#A8E6CF", - "position": { - "x": -11, - "y": -23 - }, - "parameters": { - "LLM": "llama3.1:8b" - }, - "description": null, - "type": "Requirement Generator" - }, - { - "key": "394f3a84-f8b0-4cf6-92c7-042e6be03a26", - "name": "newNode", - "createdBy": null, - "outputs": [ - { - "key": "a73eb6d8-9010-428e-a0cd-0831fcba6c42", - "type": "text", - "name": "corrected-requirements" - } - ], - "inputs": [ - { - "key": "e83aebf3-05dd-4588-b9a3-03fd7253e5fc", - "type": "text", - "name": "requirement-smells" - } - ], - "color": "#A8E6CF", - "position": { - "x": 321, - "y": -21 - }, - "parameters": { - "LLM": "gemma3:12b" - }, - "description": null, - "type": "Requirements Corrector" - }, - { - "key": "511a2b09-dde6-4877-97ed-92819da4b778", - "name": "newNode", - "createdBy": null, - "outputs": [ - { - "key": "96750771-855b-4013-9959-ac8b3fd17133", - "type": "text", - "name": "requirement-smells" - } - ], - "inputs": [ - { - "key": "059dad32-a3ea-4a34-b77e-f747c2baf864", - "type": "text", - "name": "requirements" - } - ], - "color": "#A8E6CF", - "position": { - "x": 707, - "y": -22 - }, - "parameters": { - "LLM": "codellama:latest" - }, - "description": null, - "type": "Requirements Analyzer" - } - ], - "connections": [ - { - "key": "f8b4dbaa-a9cc-4470-89a2-7b999d905d9b", - "from": "2967ca92-3977-44ad-8e19-318b9a71d227", - "to": "7860945a-506b-45a3-97b6-cbf08023e421" - }, - { - "key": "9b2da750-f8c2-425a-a4c0-67869a60f69b", - "from": "f36c7ae1-78da-4d71-8b44-58a1f87f17cb", - "to": "e83aebf3-05dd-4588-b9a3-03fd7253e5fc" - }, - { - "key": "ec5ed95b-e546-4ee2-957b-039649f3fac4", - "from": "a73eb6d8-9010-428e-a0cd-0831fcba6c42", - "to": "059dad32-a3ea-4a34-b77e-f747c2baf864" - } - ], - "public": true - }, - { - "createdBy": "lucio.lelii", - "name": "new flow", - "description": null, - "nodes": [ - { - "key": "e5f1f8d6-7717-4e32-ab57-f25f38f55401", - "name": "newNode", - "createdBy": null, - "outputs": [ - { - "key": "2967ca92-3977-44ad-8e19-318b9a71d227", - "type": "text", - "name": "value" - } - ], - "inputs": [], - "color": "#A8E6CF", - "position": { - "x": -270, - "y": -16 - }, - "parameters": { - "name": "interview" - }, - "description": null, - "type": "Text Input" - }, - { - "key": "1a4d1254-2c44-49fa-8aee-3865d1be9f95", - "name": "newNode", - "createdBy": null, - "outputs": [ - { - "key": "f36c7ae1-78da-4d71-8b44-58a1f87f17cb", - "type": "text", - "name": "requirements" - } - ], - "inputs": [ - { - "key": "7860945a-506b-45a3-97b6-cbf08023e421", - "type": "text", - "name": "interview" - } - ], - "color": "#A8E6CF", - "position": { - "x": -11, - "y": -23 - }, - "parameters": { - "LLM": "llama3.1:8b" - }, - "description": null, - "type": "Requirement Generator" - }, - { - "key": "394f3a84-f8b0-4cf6-92c7-042e6be03a26", - "name": "newNode", - "createdBy": null, - "outputs": [ - { - "key": "a73eb6d8-9010-428e-a0cd-0831fcba6c42", - "type": "text", - "name": "corrected-requirements" - } - ], - "inputs": [ - { - "key": "e83aebf3-05dd-4588-b9a3-03fd7253e5fc", - "type": "text", - "name": "requirement-smells" - } - ], - "color": "#A8E6CF", - "position": { - "x": 321, - "y": -21 - }, - "parameters": { - "LLM": "gemma3:12b" - }, - "description": null, - "type": "Requirements Corrector" - }, - { - "key": "511a2b09-dde6-4877-97ed-92819da4b778", - "name": "newNode", - "createdBy": null, - "outputs": [ - { - "key": "96750771-855b-4013-9959-ac8b3fd17133", - "type": "text", - "name": "requirement-smells" - } - ], - "inputs": [ - { - "key": "059dad32-a3ea-4a34-b77e-f747c2baf864", - "type": "text", - "name": "requirements" - } - ], - "color": "#A8E6CF", - "position": { - "x": 707, - "y": -22 - }, - "parameters": { - "LLM": "codellama:latest" - }, - "description": null, - "type": "Requirements Analyzer" - }, - { - "key": "529bb583-05fe-4eb9-b194-1de65f577155", - "name": "newNode", - "createdBy": null, - "outputs": [], - "inputs": [ - { - "key": "3677287f-e276-4869-913b-e985eedbfe08", - "type": "text", - "name": "value" - } - ], - "color": "#A8E6CF", - "position": { - "x": 1075, - "y": 118 - }, - "parameters": null, - "description": null, - "type": "Text Output" - } - ], - "connections": [ - { - "key": "f8b4dbaa-a9cc-4470-89a2-7b999d905d9b", - "from": "2967ca92-3977-44ad-8e19-318b9a71d227", - "to": "7860945a-506b-45a3-97b6-cbf08023e421" - }, - { - "key": "9b2da750-f8c2-425a-a4c0-67869a60f69b", - "from": "f36c7ae1-78da-4d71-8b44-58a1f87f17cb", - "to": "e83aebf3-05dd-4588-b9a3-03fd7253e5fc" - }, - { - "key": "ec5ed95b-e546-4ee2-957b-039649f3fac4", - "from": "a73eb6d8-9010-428e-a0cd-0831fcba6c42", - "to": "059dad32-a3ea-4a34-b77e-f747c2baf864" - }, - { - "key": "0aabf90f-d65d-4a21-8832-ad8ea6755547", - "from": "96750771-855b-4013-9959-ac8b3fd17133", - "to": "3677287f-e276-4869-913b-e985eedbfe08" - } - ], - "public": true - } -] \ No newline at end of file diff --git a/src/main/resources/workflow-editor-init/nodes.json b/src/main/resources/workflow-editor-init/nodes.json deleted file mode 100644 index 95b8d6d..0000000 --- a/src/main/resources/workflow-editor-init/nodes.json +++ /dev/null @@ -1,263 +0,0 @@ -[ - { - "name": "Requirement Generator", - "category": "Generators", - "executor": "GENERIC-AI", - "color": "black", - "createdBy": "admin", - "fixedParameters": {}, - "runtimeParameters": { - "LLM": { - "name": "LLM", - "label": "Select the LLM to use", - "description": "The LLM to use for generating requirements", - "type": "Select", - "required": true, - "validations": [], - "specificAttributes": { - "options": [ - "google-gemini", - "open-router" - ] - } - } - }, - "inputs": { - "interview": "Text" - }, - "outputs": { - "requirements": "Text" - }, - "executorToOutputTranslationMappings": { - "response": "requirements" - }, - "inputToExecutorTranslationMappings": {}, - "inputTranslators": { - "prompt": { - "translation": "Analyze the following interview transcript and extract a list of functional requirements. \n\n${{interview}}\n\nFormat the output as a CSV with the following columns: \n1. **ID**: Unique identifier (e.g., F1, F2, F3…). \n2. **Requirement Text**: Clear description of what the system must do (in plain language). \n3. **Customer Quote**: Exact phrase from the transcript that justifies the requirement. \n\n**Rules:** \n- Include **only functional requirements** (no qualities or non-functional requirements). \n- Keep requirements concise and actionable. \n- List requirements in logical order.\n- Make sure requirements are free of smells.\n- Output only the CSV, do not add any text.\n\n**CSV Format Example:**\n\nF1; The system shall ask users to select the arithmetic operation to perform. The possible arithmetic operations are addition, subtraction, multiplication, division; “I need some support to perform different arithmetic operations”.\nF2: The system shall ask users to select the first operand of the operation; “I want to express the entire operation verbally, and get the results”.\nF3: The system shall ask users to select the second operand of the operation; “I want to express the entire operation verbally, and get the results”.\n" - } - } - }, - { - "name": "Requirements Analyzer", - "category": "Analyzers", - "executor": "GENERIC-AI", - "color": "black", - "createdBy": "admin", - "fixedParameters": {}, - "runtimeParameters": { - "LLM": { - "name": "LLM", - "label": "Select the LLM to use", - "description": "The LLM to use for generating requirements", - "type": "Select", - "required": true, - "validations": [], - "specificAttributes": { - "options": [ - "google-gemini", - "open-router" - ] - } - } - }, - "inputs": { - "requirements": "Text" - }, - "outputs": { - "requirement-smells": "Text" - }, - "executorToOutputTranslationMappings": { - "response": "requirement-smells" - }, - "inputToExecutorTranslationMappings": {}, - "inputTranslators": { - "prompt": { - "translation": "giving the following requirements as csv:\n${{requirements}}\nAct as a **Requirements Analyst** tasked with reviewing a set of functional requirements derived from a customer interview. Analyze each requirement for potential **requirement smells** (e.g., ambiguity, incompleteness, inconsistency, or vagueness) and generate a CSV report with the following columns: \n\n1. **ID**: Unique identifier of the requirement (e.g., F1, F2). \n2. **Requirement**: The exact text of the requirement. \n3. **Customer Quote**: Supporting quote from the stakeholder. \n4. **Smell Description**: \n - If a smell is detected, briefly describe it (e.g., \"Ambiguous: 'user-friendly' is subjective\"). \n - If no smell is found, leave this field empty (null). \n\n**Rules:** \n- Read the requirement carefully, and think whether it can be tested\n- If it cannot be tested, understand what the smell is that makes it not testable. \n- Typical smells are: \nAmbiguity: Subjective terms (e.g., \"easy,\" \"fast\").\nIncompleteness: Missing steps/triggers (e.g., \"remind users\" but no timing specified).\nInconsistency: Conflicts with other requirements.\nVagueness: Unquantifiable metrics (e.g., \"often,\" \"sometimes\").\n- Output only the CSV, do not add any text.\n\n**Output CSV Format Example:** \n\nF1; The system shall ask users to select the arithmetic operation to perform. The possible arithmetic operations are addition, subtraction, multiplication, division; “I need some support to perform different arithmetic operations”; null\nF2; The system shall ask users to select the first operand of the operation; “I want to express the entire operation verbally, and get the results”; users is plural, can be ambiguous\n" - } - } - }, - { - "name": "Requirements Corrector", - "category": "Correctors", - "executor": "GENERIC-AI", - "color": "black", - "createdBy": "admin", - "fixedParameters": { }, - "runtimeParameters": { - "LLM": { - "name": "LLM", - "label": "Select the LLM to use", - "description": "The LLM to use for generating requirements", - "type": "Select", - "required": true, - "validations": [ ], - "specificAttributes": { - "options": [ - "google-gemini", - "open-router" - ] - } - } - }, - "inputs": { - "requirement-smells": "Text" - }, - "outputs": { - "corrected-requirements": "Text" - }, - "executorToOutputTranslationMappings": { - "response": "corrected-requirements" - }, - "inputToExecutorTranslationMappings": { }, - "inputTranslators": { - "prompt": { - "translation": "giving the input:\n\n${{requirement-smells}}\n\nthat is a CSV of requirements with identified smells (columns: `ID`, `Requirement`, `Customer Quote`, `Smell Description`). \n\nAct as a **Requirements Corrector**. Your task is to **revise smelly requirements** (ambiguity, vagueness, inconsistency, incompleteness) into **clear, actionable, and testable** functional requirements. \n\n**Output Rules:** \n- Generate a **new CSV** with columns: \n 1. **ID**: Original requirement identifier (e.g., `F1`, `F2`). \n 2. **Corrected Requirement**: Rewritten version that resolves the smell. \n 3. **Customer Quote**: Original supporting quote from the transcript. \n- **Remove the \"Smell Description\" column** (corrections should speak for themselves). \n- **Preserve intent**: Ensure the Corrected requirement aligns with the stakeholder’s original need. \n- **For non-smelly requirements**: Copy them verbatim to the new CSV.\n- Output only the CSV, do not add any text.\n\n**Output CSV Format Example:** \n\nF1; The system shall ask users to select the arithmetic operation to perform. The possible arithmetic operations are addition, subtraction, multiplication, division; “I need some support to perform different arithmetic operations”\nF2; The system shall ask the user to select the first operand of the operation; “I want to express the entire operation verbally, and get the results”;\n" - } - } - }, - { - "name": "Class Modeller (UML)", - "category": "Modellers", - "executor": "GENERIC-AI", - "color": "black", - "createdBy": "admin", - "fixedParameters": { }, - "runtimeParameters": { - "LLM": { - "name": "LLM", - "label": "Select the LLM to use", - "description": "The LLM to use for generating requirements", - "type": "Select", - "required": true, - "validations": [ ], - "specificAttributes": { - "options": [ - "google-gemini", - "open-router" - ] - } - } - }, - "inputs": { - "corrected-requirements": "Text", - "interview-script": "Text" - }, - "outputs": { - "plant-uml": "Text" - }, - "executorToOutputTranslationMappings": { - "response": "plant-uml" - }, - "inputToExecutorTranslationMappings": { }, - "inputTranslators": { - "prompt": { - "translation": "Act as a **Requirements Modeler** specializing in structural design. Create a PlantUML class diagram that captures the core domain model implied by the functional requirements and interview transcript.\n\n**Input:** \n1. Corrected requirements (CSV with `ID`, `Corrected Requirement`, `Customer Quote`) :\n\n${{corrected-requirements}}\n \n2. Interview transcript for context :\n\n${{interview-script}}\n\n**Output Rules:** \n1. Generate **one concise class diagram** as a textual output in PlantUML syntax \n2. Focus on: \n - Key entities (classes) and their attributes \n - Critical relationships (associations, compositions) \n - Basic operations/methods implied by requirements \n3. Output only the model, do not add any text.\n\n**Example Output (PlantUML):**\n\n@startuml Calculator_Class_Diagram\n\nclass Calculator {\n + currentInput: Number\n + storedValue: Number\n + selectedOperation: OperationType\n + calculate(): Number\n + clear(): void\n}\n\nclass InputHandler {\n + handleDigitPress(digit: Integer): void\n + handleOperator(op: OperationType): void\n + handleEquals(): void\n}\n\nclass Display {\n + updateScreen(value: String): void\n}\n\nenum OperationType <> {\n ADD\n SUBTRACT\n MULTIPLY\n DIVIDE\n}\n\nCalculator \"1\" *-- \"1\" OperationType\nCalculator --> Display : updates\nInputHandler --> Calculator : commands\n\nnote top of Calculator : \"Must support basic arithmetic\\noperations (F2)\"\nnote right of InputHandler : \"User quote: 'I need buttons for\\nnumbers and functions...'\"\n\n@enduml\n" - } - } - }, - { - "name": "InputDispatcher", - "category": "Dispachers", - "executor": "NoOp", - "color": "black", - "createdBy": "admin", - "fixedParameters": { }, - "runtimeParameters": { }, - "inputs": { - "input": "Text" - }, - "outputs": { - "output": "Text" - }, - "executorToOutputTranslationMappings": { - "output": "output" - }, - "inputToExecutorTranslationMappings": { - "input": "input" - }, - "inputTranslators": { } - }, - { - "name": "Sequence Modeler", - "category": "Modellers", - "executor": "GENERIC-AI", - "color": "black", - "createdBy": "admin", - "fixedParameters": { }, - "runtimeParameters": { - "LLM": { - "name": "LLM", - "label": "Select the LLM to use", - "description": "The LLM to use for generating requirements", - "type": "Select", - "required": true, - "validations": [ ], - "specificAttributes": { - "options": [ - "google-gemini", - "open-router" - ] - } - } - }, - "inputs": { - "interview": "Text", - "plantuml-class-diagram": "Text", - "corrected-requirements": "Text" - }, - "outputs": { - "plantuml-sequence-diagram": "Text" - }, - "executorToOutputTranslationMappings": { - "response": "plantuml-sequence-diagram" - }, - "inputToExecutorTranslationMappings": { }, - "inputTranslators": { - "prompt": { - "translation": "Act as a **Requirements Modeler** specializing in interaction design. Create a PlantUML sequence diagram that visually captures the key system interactions described in the functional requirements and interview transcript.\n\n**Input:**\n1. Cleaned requirements (CSV format with ID, Corrected Requirement, Customer Quote):\n\n${{corrected-requirements}}\n\n2. Original interview transcript for context:\n\n${{interview}}\n\n3. Class diagram:\n\n\n\n**Output Rules:**\n1. Generate **one comprehensive sequence diagram** in PlantUML syntax\n2. Consider only the classes from the input class diagram\n3. Focus on showing:\n - Primary user-to-system interactions\n - Critical system responses\n - Important timing constraints (where specified)\n4. Include only the most essential interactions (5-10 steps max)\n5. Output only the model, do not add any text.\n\n**Example Output (PlantUML):**\n\n@startuml Calculator_Sequence_Diagram \n\nactor User \nparticipant InputHandler \nparticipant Calculator \nparticipant Display \n\nUser -> InputHandler : Press digit \"5\" \nnote right of User: \"I need buttons for numbers...\" (Requirement F1) \nInputHandler -> Calculator : Store input (currentInput = 5) \nCalculator -> Display : Update screen (\"5\") \n\nUser -> InputHandler : Press operator \"+\" \nnote right of User: \"Basic arithmetic is essential\" (F3) \nInputHandler -> Calculator : Set operation (ADD) \n\nUser -> InputHandler : Press digit \"3\" \nInputHandler -> Calculator : Store input (currentInput = 3) \nCalculator -> Display : Update screen (\"3\") \n\nUser -> InputHandler : Press \"=\" \nInputHandler -> Calculator : calculate() \nCalculator -> Calculator : Compute 5 + 3 \nCalculator -> Display : Update screen (\"8\") \n\n@enduml \n" - } - } - }, - { - "name": "Code Generator", - "category": "Generators", - "executor": "GENERIC-AI", - "color": "black", - "createdBy": "admin", - "fixedParameters": { }, - "runtimeParameters": { - "LLM": { - "name": "LLM", - "label": "Select the LLM to use", - "description": "The LLM to use for generating requirements", - "type": "Select", - "required": true, - "validations": [ ], - "specificAttributes": { - "options": [ - "google-gemini", - "open-router" - ] - } - } - }, - "inputs": { - "corrected-requirements": "Text", - "class-diagram": "Text", - "sequence-diagram": "Text" - }, - "outputs": { - "source-code": "Text" - }, - "executorToOutputTranslationMappings": { - "response": "source-code" - }, - "inputToExecutorTranslationMappings": { }, - "inputTranslators": { - "prompt": { - "translation": "Act as a **Senior Software Engineer** specializing in **model-driven development**. Generate clean, maintainable code that:\n1. Implements all validated requirements (from CSV) \n2. Matches the structure of the provided class diagram \n3. Follows the interaction flows from the sequence diagram \n\n**Input:** \n1. requirements (ID, Corrected Requirement, Customer Quote):\n\n${{corrected-requirements}}\n\n2. class diagram (PlantUML) :\n\n${{class-diagram}}\n\n3. sequence diagram (PlantUML) :\n\n${{sequence-diagram}}\n\n**Output Rules:** \n- **Language**: Python\n- **Architecture**: \n - 1:1 mapping of PlantUML classes to code \n - Sequence diagram steps → method logic \n- **Quality**: \n - Type hints (Python)\n - Docstrings referencing requirement IDs (e.g., `# F1`) \n - Appropriate error handling \n- **Format**:\n - Output only executable code, do not add any text, besides comments\n\n**Example Output (Python Calculator):** \n\nfrom enum import Enum, auto\n\nclass OperationType(Enum):\n \"\"\"Supported operations (matches class diagram <>)\"\"\"\n ADD = auto() # F3: Basic arithmetic\n SUBTRACT = auto() # F3\n MULTIPLY = auto() # F3\n DIVIDE = auto() # F3\n\nclass Calculator:\n \"\"\"Core calculator logic (from class diagram)\"\"\"\n def __init__(self):\n self.current_input = 0 # F1: Digit entry\n self.stored_value = None # For multi-step operations\n self.selected_op = None # F3: Operation selection\n \n def calculate(self) -> float:\n \"\"\"Execute the current operation (sequence diagram '=' press)\"\"\"\n if not self.selected_op:\n return self.current_input\n \n # Sequence diagram compute step\n match self.selected_op:\n case OperationType.ADD:\n result = self.stored_value + self.current_input\n case OperationType.SUBTRACT:\n result = self.stored_value - self.current_input\n # ... other operations (F3)\n \n self.current_input = result\n return result\n\nclass InputHandler:\n \"\"\"Manages user input flow (sequence diagram interactions)\"\"\"\n def __init__(self, calculator: Calculator, display: Display):\n self.calc = calculator\n self.display = display\n \n def handle_digit(self, digit: int): # F1\n \"\"\"Process digit press (sequence diagram step 1)\"\"\"\n self.calc.current_input = self.calc.current_input * 10 + digit\n self.display.update(str(self.calc.current_input))\n\n def handle_operator(self, op: OperationType): # F3\n \"\"\"Process +,-,*,/ press (sequence diagram step 2)\"\"\"\n self.calc.stored_value = self.calc.current_input\n self.calc.selected_op = op\n self.calc.current_input = 0\n" - } - } - } -] \ No newline at end of file diff --git a/src/main/resources/workflow-editor-init/users.json b/src/main/resources/workflow-editor-init/users.json new file mode 100644 index 0000000..98aeec7 --- /dev/null +++ b/src/main/resources/workflow-editor-init/users.json @@ -0,0 +1,6 @@ +[ + { + "username": "testuser", + "password": "testpassword" + } +] diff --git a/src/test/java/it/cnr/isti/workflow/manager/MyTestConfiguration.java b/src/test/java/it/cnr/isti/workflow/manager/MyTestConfiguration.java index b8dcef0..3958c47 100644 --- a/src/test/java/it/cnr/isti/workflow/manager/MyTestConfiguration.java +++ b/src/test/java/it/cnr/isti/workflow/manager/MyTestConfiguration.java @@ -4,35 +4,27 @@ import java.util.List; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.web.SecurityFilterChain; +import org.springframework.context.annotation.Profile; +import it.cnr.isti.workflow.manager.bricks.Brick; import it.cnr.isti.workflow.manager.bricks.BrickManager; -import it.cnr.isti.workflow.manager.bricks.LLMBrick; @TestConfiguration +@Profile("test") public class MyTestConfiguration { - @Bean(name = "brickTestManager") - BrickManager brickManager() { - return new BrickManager() { + @Bean(name = "testProvider") + BrickManager brickManager() { + return new BrickManager() { @Override - public List possibleValues(String key) { - return null; - } - - @Override - public List getBricks() { - return List.of(new LLMBrick("llmProvider", "model", "provider")); + public List getBricks() { + Brick b = new Brick("id", "testProvider") { + }; + + return List.of(b); } }; } - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - return http.csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) - .build(); - } } diff --git a/src/test/java/it/cnr/isti/workflow/manager/TestSecurityConfig.java b/src/test/java/it/cnr/isti/workflow/manager/TestSecurityConfig.java new file mode 100644 index 0000000..e61c72d --- /dev/null +++ b/src/test/java/it/cnr/isti/workflow/manager/TestSecurityConfig.java @@ -0,0 +1,20 @@ +package it.cnr.isti.workflow.manager; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@TestConfiguration +@Profile("test") +public class TestSecurityConfig { + + @Bean(name = "testSecurityFilterChain") + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) + .csrf(csrf -> csrf.disable()) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/it/cnr/isti/workflow/manager/bricks/configurations/TestConfig.java b/src/test/java/it/cnr/isti/workflow/manager/bricks/configurations/TestConfig.java index 49bd9e3..a6eafd6 100644 --- a/src/test/java/it/cnr/isti/workflow/manager/bricks/configurations/TestConfig.java +++ b/src/test/java/it/cnr/isti/workflow/manager/bricks/configurations/TestConfig.java @@ -16,6 +16,6 @@ public class TestConfig { @Test public void generateJSONSchema() throws Exception { - System.out.println(schemaProducer.generateSchema(LLMBrickConfiguration.class)); + } } diff --git a/src/test/java/it/cnr/isti/workflow/manager/controllers/AuthControllerTest.java b/src/test/java/it/cnr/isti/workflow/manager/controllers/AuthControllerTest.java new file mode 100644 index 0000000..7a73c3c --- /dev/null +++ b/src/test/java/it/cnr/isti/workflow/manager/controllers/AuthControllerTest.java @@ -0,0 +1,23 @@ +package it.cnr.isti.workflow.manager.controllers; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; + +import it.cnr.isti.workflow.manager.auth.model.AuthRequest; + +@SpringBootTest +@TestPropertySource(locations = "classpath:test.properties") +public class AuthControllerTest { + + @Autowired + private AuthController authController; + + @Test + public void testLogin() { + ResponseEntity response = authController.login(new AuthRequest("testuser", "testpassword")); + assert response.getStatusCode().is2xxSuccessful(); + } +} diff --git a/src/test/java/it/cnr/isti/workflow/manager/controllers/BlocksControllerTest.java b/src/test/java/it/cnr/isti/workflow/manager/controllers/BlocksControllerTest.java index adf13d5..11a604a 100644 --- a/src/test/java/it/cnr/isti/workflow/manager/controllers/BlocksControllerTest.java +++ b/src/test/java/it/cnr/isti/workflow/manager/controllers/BlocksControllerTest.java @@ -8,16 +8,29 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; +import it.cnr.isti.workflow.manager.MyTestConfiguration; import it.cnr.isti.workflow.manager.blocks.Block; import it.cnr.isti.workflow.manager.blocks.configurations.LLMBlockConfiguration; import it.cnr.isti.workflow.manager.blocks.types.BlockType; import it.cnr.isti.workflow.manager.blocks.types.LLMBlockType; +import it.cnr.isti.workflow.manager.bricks.BrickManager; +import it.cnr.isti.workflow.manager.bricks.LLMBrick; @SpringBootTest +@Import(MyTestConfiguration.class) +@ActiveProfiles("test") public class BlocksControllerTest { + + @Qualifier("brickTestManager") + @Autowired + BrickManager llmBrickManager; + @Autowired private BlocksController blocksController; @@ -36,6 +49,7 @@ public class BlocksControllerTest { LLMBlockConfiguration config = LLMBlockConfiguration.builder() .prompt("Hello, ${{name}}!") .name("master") + .brick(llmBrickManager.getBricks().get(0)) .build(); Block block = blocksController.create(config); assertNotNull(block); diff --git a/src/test/java/it/cnr/isti/workflow/manager/controllers/BricksControllerTest.java b/src/test/java/it/cnr/isti/workflow/manager/controllers/BricksControllerTest.java index a945ad5..4ee0b79 100644 --- a/src/test/java/it/cnr/isti/workflow/manager/controllers/BricksControllerTest.java +++ b/src/test/java/it/cnr/isti/workflow/manager/controllers/BricksControllerTest.java @@ -2,29 +2,37 @@ package it.cnr.isti.workflow.manager.controllers; 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.test.web.servlet.MockMvc; import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; import it.cnr.isti.workflow.manager.MyTestConfiguration; +import it.cnr.isti.workflow.manager.bricks.Brick; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; -@SpringBootTest -@AutoConfigureMockMvc +import java.util.List; + +@SpringBootTest() @Import(MyTestConfiguration.class) +@ActiveProfiles("test") public class BricksControllerTest { @Autowired - private MockMvc mockMvc; + private BricksController bricksController; @Test public void testGetBricks() throws Exception { - mockMvc.perform(get("/bricks/providers/testProvider")) - .andExpect(status().isOk()) - .andExpect(content().json("[{\"id\":\"provider:llmProvider_model\",\"provider\":\"llmProvider\",\"model\":\"model\"}]")); + String provider = bricksController.getProviders().stream().filter(p -> p.equals("testProvider")).findFirst().orElse(null); + assertNotNull(provider); + assertEquals("testProvider", provider); + List bricks = bricksController.getBricksByProvider(provider); + assertTrue(!bricks.isEmpty()); + assertEquals(1, bricks.size()); + Brick brick = bricks.get(0); + assertNotNull(brick); + assertEquals("testProvider:id", brick.getId()); } } diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties index b1eb62d..15423ac 100644 --- a/src/test/resources/test.properties +++ b/src/test/resources/test.properties @@ -8,5 +8,5 @@ spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.jakarta.persistence.validation.mode=none app.db.init.enabled=true -app.import.path=src/main/resources/workflow-editor-init -app.import.enabled=false \ No newline at end of file +app.import.path=src/test/resources/workflow-editor-init +app.import.enabled=true \ No newline at end of file diff --git a/src/test/resources/workflow-editor-init/users.json b/src/test/resources/workflow-editor-init/users.json new file mode 100644 index 0000000..98aeec7 --- /dev/null +++ b/src/test/resources/workflow-editor-init/users.json @@ -0,0 +1,6 @@ +[ + { + "username": "testuser", + "password": "testpassword" + } +]