Implemeting spring security auth
This commit is contained in:
parent
344477881f
commit
0afb90c068
@ -5,11 +5,11 @@ import com.vaadin.flow.spring.security.VaadinWebSecurity;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@ -25,13 +25,18 @@ public class SecurityConfig extends VaadinWebSecurity {
|
|||||||
setLoginView(http, LoginView.class);
|
setLoginView(http, LoginView.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
public AuthenticationManager authenticationManager() {
|
|
||||||
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://ldap.primefactorsolutions.com:389/dc=primefactorsolutions,dc=com");
|
|
||||||
contextSource.setCacheEnvironmentProperties(false);
|
|
||||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
|
|
||||||
factory.setUserDnPatterns("uid={0},ou=users");
|
|
||||||
|
|
||||||
return factory.createAuthenticationManager();
|
@Bean
|
||||||
|
public BCryptPasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DaoAuthenticationProvider authenticationProvider(final UserDetailsService userDetailsService) {
|
||||||
|
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
|
authProvider.setUserDetailsService(userDetailsService);
|
||||||
|
authProvider.setPasswordEncoder(passwordEncoder());
|
||||||
|
|
||||||
|
return authProvider;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,4 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public interface UserRepository extends JpaRepository<User, UUID> {
|
public interface UserRepository extends JpaRepository<User, UUID> {
|
||||||
|
User getByEmail(String email);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.primefactorsolutions.invoices.model;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
public enum Permission implements GrantedAuthority {
|
||||||
|
INVALIDATE_INVOICE,
|
||||||
|
EDIT_COMPANY;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,24 @@
|
|||||||
package com.primefactorsolutions.invoices.model;
|
package com.primefactorsolutions.invoices.model;
|
||||||
|
|
||||||
public enum Role {
|
import lombok.Getter;
|
||||||
ADMIN,
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
REGULAR
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum Role implements GrantedAuthority {
|
||||||
|
ADMIN(List.of(Permission.EDIT_COMPANY, Permission.INVALIDATE_INVOICE)),
|
||||||
|
REGULAR(List.of());
|
||||||
|
|
||||||
|
private final List<Permission> permissions;
|
||||||
|
|
||||||
|
Role(final List<Permission> permissions) {
|
||||||
|
this.permissions = permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return this.name();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,33 @@
|
|||||||
package com.primefactorsolutions.invoices.model;
|
package com.primefactorsolutions.invoices.model;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import io.hypersistence.utils.hibernate.type.json.JsonType;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.*;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "user_")
|
@Table(name = "user_")
|
||||||
@Data
|
@Data
|
||||||
public class User extends AbstractEntity {
|
public class User extends AbstractEntity implements UserDetails {
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String email;
|
private String email;
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String fullName;
|
private String fullName;
|
||||||
|
|
||||||
private Role role;
|
@Type(JsonType.class)
|
||||||
|
@Column(columnDefinition = "json")
|
||||||
|
private List<Role> roles;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private Status status;
|
private Status status;
|
||||||
@ -28,4 +36,34 @@ public class User extends AbstractEntity {
|
|||||||
@JoinColumn(name = "company_id")
|
@JoinColumn(name = "company_id")
|
||||||
@NotNull
|
@NotNull
|
||||||
private Company company;
|
private Company company;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return this.roles.stream().flatMap(r -> r.getPermissions().stream()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return this.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return this.status != Status.DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return this.status != Status.DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return this.status == Status.ACTIVE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.primefactorsolutions.invoices.services;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.button.Button;
|
||||||
|
import com.vaadin.flow.component.button.ButtonVariant;
|
||||||
|
import com.vaadin.flow.i18n.I18NProvider;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Data
|
||||||
|
public class ComponentBuilder {
|
||||||
|
private final I18NProvider i18NProvider;
|
||||||
|
|
||||||
|
public ButtonBuilder button(String text) {
|
||||||
|
return ButtonBuilder.builder().i18NProvider(i18NProvider).text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ButtonBuilder {
|
||||||
|
private String text;
|
||||||
|
private I18NProvider i18NProvider;
|
||||||
|
private ButtonVariant buttonVariant;
|
||||||
|
|
||||||
|
public static ButtonBuilder builder() {
|
||||||
|
return new ButtonBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Button build() {
|
||||||
|
final Button button = new Button(i18NProvider.getTranslation(text, Locale.of("es")));
|
||||||
|
|
||||||
|
if (buttonVariant != null) {
|
||||||
|
button.addThemeVariants(buttonVariant);
|
||||||
|
}
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ButtonBuilder primary() {
|
||||||
|
this.setButtonVariant(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ButtonBuilder i18NProvider(I18NProvider i18NProvider) {
|
||||||
|
this.setI18NProvider(i18NProvider);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ButtonBuilder text(String text) {
|
||||||
|
this.setText(text);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,9 @@ import com.primefactorsolutions.invoices.model.Role;
|
|||||||
import com.primefactorsolutions.invoices.model.Status;
|
import com.primefactorsolutions.invoices.model.Status;
|
||||||
import com.primefactorsolutions.invoices.model.User;
|
import com.primefactorsolutions.invoices.model.User;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -12,7 +15,7 @@ import java.util.UUID;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Data
|
@Data
|
||||||
public class UserService {
|
public class UserService implements UserDetailsService {
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final CompanyService companyService;
|
private final CompanyService companyService;
|
||||||
|
|
||||||
@ -28,10 +31,15 @@ public class UserService {
|
|||||||
if (updatedUser.getId() == null) {
|
if (updatedUser.getId() == null) {
|
||||||
var company = companyService.getCompany();
|
var company = companyService.getCompany();
|
||||||
updatedUser.setCompany(company);
|
updatedUser.setCompany(company);
|
||||||
updatedUser.setRole(Role.REGULAR);
|
updatedUser.setRoles(List.of(Role.REGULAR));
|
||||||
updatedUser.setStatus(Status.ACTIVE);
|
updatedUser.setStatus(Status.ACTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
userRepository.save(updatedUser);
|
userRepository.save(updatedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
return userRepository.getByEmail(username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,9 @@ package com.primefactorsolutions.invoices.views;
|
|||||||
|
|
||||||
import com.primefactorsolutions.invoices.model.Client;
|
import com.primefactorsolutions.invoices.model.Client;
|
||||||
import com.primefactorsolutions.invoices.services.ClientService;
|
import com.primefactorsolutions.invoices.services.ClientService;
|
||||||
|
import com.primefactorsolutions.invoices.services.ComponentBuilder;
|
||||||
import com.primefactorsolutions.invoices.views.component.Breadcrumbs;
|
import com.primefactorsolutions.invoices.views.component.Breadcrumbs;
|
||||||
import com.primefactorsolutions.invoices.views.component.GenericForm;
|
import com.primefactorsolutions.invoices.views.component.GenericForm;
|
||||||
import com.vaadin.flow.component.button.Button;
|
|
||||||
import com.vaadin.flow.component.button.ButtonVariant;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.i18n.I18NProvider;
|
import com.vaadin.flow.i18n.I18NProvider;
|
||||||
@ -20,7 +19,6 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static com.primefactorsolutions.invoices.utils.UiUtils.goTo;
|
import static com.primefactorsolutions.invoices.utils.UiUtils.goTo;
|
||||||
@ -34,26 +32,26 @@ public class ClientEditView extends VerticalLayout implements HasUrlParameter<St
|
|||||||
|
|
||||||
ClientService clientService;
|
ClientService clientService;
|
||||||
I18NProvider i18NProvider;
|
I18NProvider i18NProvider;
|
||||||
|
ComponentBuilder componentBuilder;
|
||||||
GenericForm<Client> clientGenericForm;
|
GenericForm<Client> clientGenericForm;
|
||||||
|
|
||||||
public ClientEditView(ClientService clientService, I18NProvider i18NProvider) {
|
public ClientEditView(ClientService clientService, I18NProvider i18NProvider, ComponentBuilder componentBuilder) {
|
||||||
this.clientService = clientService;
|
this.clientService = clientService;
|
||||||
this.i18NProvider = i18NProvider;
|
this.i18NProvider = i18NProvider;
|
||||||
|
this.componentBuilder = componentBuilder;
|
||||||
|
|
||||||
var client = new Client();
|
var client = new Client();
|
||||||
|
|
||||||
this.clientGenericForm = new GenericForm<>(Client.class);
|
this.clientGenericForm = new GenericForm<>(Client.class);
|
||||||
this.clientGenericForm.setBean(client);
|
this.clientGenericForm.setBean(client);
|
||||||
|
|
||||||
String text = this.i18NProvider.getTranslation("action.save", Locale.of("es"));
|
var saveButton = this.componentBuilder.button("action.save").primary().build();
|
||||||
var saveButton = new Button(text);
|
|
||||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
|
||||||
saveButton.addClickListener(c -> {
|
saveButton.addClickListener(c -> {
|
||||||
var updatedClient = clientGenericForm.getBean();
|
var updatedClient = clientGenericForm.getBean();
|
||||||
clientService.saveOrUpdateClient(updatedClient);
|
clientService.saveOrUpdateClient(updatedClient);
|
||||||
goTo(this, "clients");
|
goTo(this, "clients");
|
||||||
});
|
});
|
||||||
var cancelButton = new Button("Cancel");
|
var cancelButton = this.componentBuilder.button("action.cancel").build();
|
||||||
cancelButton.addClickListener(c -> goTo(this, "clients"));
|
cancelButton.addClickListener(c -> goTo(this, "clients"));
|
||||||
|
|
||||||
var breadcrumbs = new Breadcrumbs(List.of(Pair.of("Clientes", "clients"), Pair.of("Editar", null)));
|
var breadcrumbs = new Breadcrumbs(List.of(Pair.of("Clientes", "clients"), Pair.of("Editar", null)));
|
||||||
|
@ -18,6 +18,7 @@ import jakarta.annotation.security.PermitAll;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.vaadin.firitin.components.button.VButton;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -34,7 +35,6 @@ public class ProductEditView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
|
|
||||||
ProductService productService;
|
ProductService productService;
|
||||||
I18NProvider i18NProvider;
|
I18NProvider i18NProvider;
|
||||||
|
|
||||||
GenericForm<Product> productGenericForm;
|
GenericForm<Product> productGenericForm;
|
||||||
|
|
||||||
public ProductEditView(ProductService productService, I18NProvider i18NProvider) {
|
public ProductEditView(ProductService productService, I18NProvider i18NProvider) {
|
||||||
@ -54,9 +54,7 @@ public class ProductEditView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
goTo(this, "products");
|
goTo(this, "products");
|
||||||
});
|
});
|
||||||
var cancelButton = new Button("Cancel");
|
var cancelButton = new Button("Cancel");
|
||||||
cancelButton.addClickListener(c -> {
|
cancelButton.addClickListener(c -> goTo(this, "products"));
|
||||||
goTo(this, "products");
|
|
||||||
});
|
|
||||||
|
|
||||||
var breadcrumbs = new Breadcrumbs(List.of(Pair.of("Products", "products"), Pair.of("Editar", null)));
|
var breadcrumbs = new Breadcrumbs(List.of(Pair.of("Products", "products"), Pair.of("Editar", null)));
|
||||||
var buttonLayout = new HorizontalLayout(saveButton, cancelButton);
|
var buttonLayout = new HorizontalLayout(saveButton, cancelButton);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.primefactorsolutions.invoices.views;
|
package com.primefactorsolutions.invoices.views.invoices;
|
||||||
|
|
||||||
import com.primefactorsolutions.invoices.beans.CabeceraDTO;
|
import com.primefactorsolutions.invoices.beans.CabeceraDTO;
|
||||||
import com.primefactorsolutions.invoices.beans.ClientDTO;
|
import com.primefactorsolutions.invoices.beans.ClientDTO;
|
||||||
@ -10,6 +10,7 @@ import com.primefactorsolutions.invoices.model.mappers.DetalleMapper;
|
|||||||
import com.primefactorsolutions.invoices.services.ClientService;
|
import com.primefactorsolutions.invoices.services.ClientService;
|
||||||
import com.primefactorsolutions.invoices.services.CompanyService;
|
import com.primefactorsolutions.invoices.services.CompanyService;
|
||||||
import com.primefactorsolutions.invoices.services.InvoiceService;
|
import com.primefactorsolutions.invoices.services.InvoiceService;
|
||||||
|
import com.primefactorsolutions.invoices.views.MainLayout;
|
||||||
import com.primefactorsolutions.invoices.views.component.invoice.*;
|
import com.primefactorsolutions.invoices.views.component.invoice.*;
|
||||||
import com.primefactorsolutions.invoices.xsd.FacturaComputarizadaComercialExportacionServicio;
|
import com.primefactorsolutions.invoices.xsd.FacturaComputarizadaComercialExportacionServicio;
|
||||||
import com.vaadin.flow.component.*;
|
import com.vaadin.flow.component.*;
|
@ -14,5 +14,8 @@ INSERT INTO "PRODUCT" (ID, VERSION, COMPANY_ID, STATUS, codigo_Producto_Sin, cod
|
|||||||
precio_Unitario) VALUES ('dcecb825-7bc6-402d-850a-9d4a08fe6663', 1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 1, 1,
|
precio_Unitario) VALUES ('dcecb825-7bc6-402d-850a-9d4a08fe6663', 1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 1, 1,
|
||||||
'PROD001', 'Test product', 1, 1);
|
'PROD001', 'Test product', 1, 1);
|
||||||
|
|
||||||
INSERT INTO "USER_" (ID, VERSION, COMPANY_ID, EMAIL, FULL_NAME, ROLE, STATUS) VALUES ('b9f1bbe0-4542-48d3-b88e-452f2891cb68',
|
INSERT INTO "USER_" (ID, VERSION, COMPANY_ID, EMAIL, FULL_NAME, ROLES, PASSWORD, STATUS) VALUES ('b9f1bbe0-4542-48d3-b88e-452f2891cb68',
|
||||||
1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 'alex@test.com', 'alex prudencio', 1, 1);
|
1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 'admin@test.com', 'admin test', '[0]' FORMAT JSON, '$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', 0);
|
||||||
|
|
||||||
|
INSERT INTO "USER_" (ID, VERSION, COMPANY_ID, EMAIL, FULL_NAME, ROLES, PASSWORD, STATUS) VALUES ('547274a6-bb73-462d-85ab-b529fde798fb',
|
||||||
|
1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 'user@test.com', 'user test', '[1]' FORMAT JSON, '$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', 0);
|
@ -1 +1,2 @@
|
|||||||
action.save=Save
|
action.save=Save
|
||||||
|
action.cancel=Cancel
|
@ -1 +1,2 @@
|
|||||||
action.save=Guardar
|
action.save=Guardar
|
||||||
|
action.cancel=Cancelar
|
@ -1,12 +1,7 @@
|
|||||||
package com.primefactorsolutions.invoices.views.list;
|
package com.primefactorsolutions.invoices.views.list;
|
||||||
|
|
||||||
import com.primefactorsolutions.invoices.views.FacturaComputarizadaComercialExportacionesServicioEditView;
|
|
||||||
import com.vaadin.flow.component.Component;
|
|
||||||
import org.assertj.core.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class InvoiceFormTest {
|
public class InvoiceFormTest {
|
||||||
@Test
|
@Test
|
||||||
public void formShownWhenContactSelected() {
|
public void formShownWhenContactSelected() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user