Erweiterungen
This commit is contained in:
@@ -1,24 +1,31 @@
|
||||
package de.assecutor.emulatorstation;
|
||||
|
||||
import com.vaadin.flow.component.page.AppShellConfigurator;
|
||||
import com.vaadin.flow.component.page.Push;
|
||||
import com.vaadin.flow.theme.Theme;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.Map;
|
||||
|
||||
@SpringBootApplication
|
||||
@Push
|
||||
@Theme("default")
|
||||
public class Application implements AppShellConfigurator {
|
||||
|
||||
@Bean
|
||||
public Clock clock() {
|
||||
return Clock.systemDefaultZone(); // You can also use Clock.systemUTC()
|
||||
}
|
||||
public static final Map<String, String> users = Map.ofEntries(
|
||||
Map.entry("admin", "ZY6X9X93Co8m"),
|
||||
Map.entry("GFL", "GFL123"),
|
||||
Map.entry("Berlin", "Berlin123"),
|
||||
Map.entry("Bremen", "Bremen123"),
|
||||
Map.entry("Hamburg", "Hamburg123"),
|
||||
Map.entry("Essen", "Essen123"),
|
||||
Map.entry("Leipzig", "Leipzig123"),
|
||||
Map.entry("Dresden", "Dresden123"),
|
||||
Map.entry("Hannover", "Hannover123"),
|
||||
Map.entry("Stuttgart", "Stuttgart123"),
|
||||
Map.entry("FrankfurtAmMain", "FrankfurtAmMain123")
|
||||
);
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package de.assecutor.emulatorstation.base.domain;
|
||||
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.util.ProxyUtils;
|
||||
|
||||
@MappedSuperclass
|
||||
public abstract class AbstractEntity<ID> {
|
||||
|
||||
public abstract @Nullable ID getId();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "%s{id=%s}".formatted(getClass().getSimpleName(), getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Hashcode should never change during the lifetime of an object. Because of
|
||||
// this we can't use getId() to calculate the hashcode. Unless you have sets
|
||||
// with lots of entities in them, returning the same hashcode should not be a
|
||||
// problem.
|
||||
return ProxyUtils.getUserClass(getClass()).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
} else if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var thisUserClass = ProxyUtils.getUserClass(getClass());
|
||||
var otherUserClass = ProxyUtils.getUserClass(obj);
|
||||
if (thisUserClass != otherUserClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var id = getId();
|
||||
return id != null && id.equals(((AbstractEntity<?>) obj).getId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* This package contains reusable domain classes.
|
||||
*/
|
||||
@NullMarked
|
||||
package de.assecutor.emulatorstation.base.domain;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,75 @@
|
||||
package de.assecutor.emulatorstation.base.ui.view;
|
||||
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.html.Main;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.VaadinService;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import util.PreferencesKeyValueStore;
|
||||
import util.Util;
|
||||
|
||||
@Route("admin")
|
||||
@PageTitle("Admin")
|
||||
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Admin")
|
||||
@PermitAll // When security is enabled, allow all authenticated users
|
||||
public class AdminView extends Main {
|
||||
final TextField serverPortTextView;
|
||||
final Button saveBtn;
|
||||
final Button logoutBtn;
|
||||
|
||||
final PreferencesKeyValueStore preferences = new PreferencesKeyValueStore();
|
||||
|
||||
public AdminView() {
|
||||
addClassName(LumoUtility.Padding.MEDIUM);
|
||||
|
||||
var verticalLayout = new VerticalLayout();
|
||||
add(verticalLayout);
|
||||
|
||||
serverPortTextView = new TextField();
|
||||
serverPortTextView.setPlaceholder("Server");
|
||||
serverPortTextView.setValue("172.16.0.158");
|
||||
serverPortTextView.setMinWidth("20em");
|
||||
verticalLayout.add(serverPortTextView);
|
||||
|
||||
var serverPort = preferences.get("server");
|
||||
serverPort.ifPresent(serverPortTextView::setValue);
|
||||
|
||||
var horizontalLayout = new HorizontalLayout();
|
||||
add(horizontalLayout);
|
||||
|
||||
saveBtn = new Button("Save", event -> savePreferences());
|
||||
saveBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
horizontalLayout.add(saveBtn);
|
||||
|
||||
logoutBtn = new Button("Logout", event -> logout());
|
||||
logoutBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
horizontalLayout.add(logoutBtn);
|
||||
|
||||
setSizeFull();
|
||||
}
|
||||
|
||||
private void logout() {
|
||||
VaadinService.getCurrentRequest().getWrappedSession().invalidate();
|
||||
|
||||
// Navigiere den Benutzer zur Login-Seite
|
||||
UI.getCurrent().navigate("");
|
||||
}
|
||||
|
||||
private void savePreferences() {
|
||||
var preferences = new PreferencesKeyValueStore();
|
||||
|
||||
preferences.put("server", serverPortTextView.getValue());
|
||||
|
||||
Notification.show("Daten gespeichert!", 3000, Notification.Position.MIDDLE);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package de.assecutor.emulatorstation.base.ui.view;
|
||||
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.PasswordField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.VaadinService;
|
||||
import com.vaadin.flow.server.VaadinSession;
|
||||
import de.assecutor.emulatorstation.Application;
|
||||
|
||||
@Route("")
|
||||
public class LoginView extends VerticalLayout {
|
||||
|
||||
public LoginView() {
|
||||
setAlignItems(Alignment.CENTER);
|
||||
|
||||
Span title = new Span("Anmelden");
|
||||
title.getStyle().set("font-size", "24px").set("font-weight", "bold");
|
||||
|
||||
TextField usernameField = new TextField("Benutzername");
|
||||
PasswordField passwordField = new PasswordField("Passwort");
|
||||
|
||||
Button loginButton = new Button("Login", event -> {
|
||||
String username = usernameField.getValue();
|
||||
String password = passwordField.getValue();
|
||||
|
||||
if (authenticate(username, password)) {
|
||||
var currentSession = VaadinSession.getCurrent().getSession();
|
||||
currentSession.setMaxInactiveInterval(60); // Timeout in Sekunden
|
||||
currentSession.setAttribute("username", username);
|
||||
|
||||
VaadinService.getCurrentRequest().getWrappedSession().setAttribute("user", username);
|
||||
Notification.show("Anmeldung erfolgreich!");
|
||||
|
||||
if (username.equals("admin")) {
|
||||
getUI().ifPresent(ui -> ui.navigate(AdminView.class));
|
||||
} else {
|
||||
getUI().ifPresent(ui -> ui.navigate(MainView.class));
|
||||
}
|
||||
} else {
|
||||
Notification.show("Anmeldung fehlgeschlagen. Bitte überprüfen Sie Ihre Daten!", 3000, Notification.Position.MIDDLE);
|
||||
}
|
||||
});
|
||||
|
||||
loginButton.getStyle().set("margin-top", "10px");
|
||||
add(title, usernameField, passwordField, loginButton);
|
||||
|
||||
MainLayout.instance.setDrawerOpened(false);
|
||||
}
|
||||
|
||||
// Einfache Authentifizierung (hier nur als Beispiel, später durch echte Logik ersetzen)
|
||||
private boolean authenticate(String username, String password) {
|
||||
if (Application.users.containsKey(username)) {
|
||||
var userPassword = Application.users.get(username);
|
||||
|
||||
return userPassword.equals(password);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.assecutor.emulatorstation.base.ui.view;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.applayout.AppLayout;
|
||||
import com.vaadin.flow.component.avatar.Avatar;
|
||||
import com.vaadin.flow.component.avatar.AvatarVariant;
|
||||
@@ -14,6 +15,7 @@ import com.vaadin.flow.component.orderedlayout.Scroller;
|
||||
import com.vaadin.flow.component.sidenav.SideNav;
|
||||
import com.vaadin.flow.component.sidenav.SideNavItem;
|
||||
import com.vaadin.flow.router.Layout;
|
||||
import com.vaadin.flow.server.VaadinSession;
|
||||
import com.vaadin.flow.server.menu.MenuConfiguration;
|
||||
import com.vaadin.flow.server.menu.MenuEntry;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
@@ -23,12 +25,41 @@ import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
||||
@Layout
|
||||
@PermitAll // When security is enabled, allow all authenticated users
|
||||
public final class MainLayout extends AppLayout {
|
||||
public static MainLayout instance = null;
|
||||
|
||||
MainLayout() {
|
||||
instance = this;
|
||||
|
||||
startPolling();
|
||||
|
||||
setPrimarySection(Section.DRAWER);
|
||||
addToDrawer(createHeader(), new Scroller(createSideNav()), createUserMenu());
|
||||
|
||||
setDrawerOpened(false);
|
||||
}
|
||||
|
||||
private void startPolling() {
|
||||
UI ui = UI.getCurrent();
|
||||
ui.setPollInterval(60000); // Poll-Intervall auf 60 Sekunden setzen
|
||||
|
||||
// Session expiration handling
|
||||
ui.addPollListener(event -> {
|
||||
if (!isUserSessionValid()) {
|
||||
VaadinSession.getCurrent().getSession().invalidate(); // Session invalidieren
|
||||
ui.access(() -> ui.navigate("login")); // Benutzer zur Login-Seite weiterleiten
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isUserSessionValid() {
|
||||
// Logik für Sitzungsprüfung, z.B. Timeout-Zeit überprüfen
|
||||
return VaadinSession.getCurrent() != null
|
||||
&& VaadinSession.getCurrent().getSession() != null
|
||||
&& VaadinSession.getCurrent().getSession().getLastAccessedTime() + 300000
|
||||
> System.currentTimeMillis(); // 300000 ms = 5 Minuten
|
||||
}
|
||||
|
||||
|
||||
private Div createHeader() {
|
||||
// TODO Replace with real application logo and name
|
||||
var appLogo = VaadinIcon.CUBES.create();
|
||||
@@ -76,5 +107,4 @@ public final class MainLayout extends AppLayout {
|
||||
|
||||
return userMenu;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,33 +1,423 @@
|
||||
package de.assecutor.emulatorstation.base.ui.view;
|
||||
|
||||
import de.assecutor.emulatorstation.base.ui.component.ViewToolbar;
|
||||
import com.vaadin.flow.component.ClientCallable;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.html.IFrame;
|
||||
import com.vaadin.flow.component.html.Paragraph;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.server.VaadinService;
|
||||
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.Main;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import de.assecutor.emulatorstation.pojo.ExecResponse;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import util.Util;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* This view shows up when a user navigates to the root ('/') of the application.
|
||||
*/
|
||||
@Route
|
||||
@PermitAll // When security is enabled, allow all authenticated users
|
||||
public final class MainView extends Main {
|
||||
@Route("main")
|
||||
public final class MainView extends Main implements BeforeEnterObserver
|
||||
{
|
||||
private final String username;
|
||||
private final String server;
|
||||
|
||||
// TODO Replace with your own main view.
|
||||
private final IFrame webView;
|
||||
|
||||
private UI ui;
|
||||
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
MainView() {
|
||||
try {
|
||||
} catch (Exception ex) {
|
||||
showWaitDialog("Fehler!\n" + ex.toString());
|
||||
}
|
||||
|
||||
var currentSession = VaadinService.getCurrentRequest().getWrappedSession();
|
||||
username = Util.getSessionAttributeWithDefault(currentSession, "username", null);
|
||||
server = Util.getSessionAttributeWithDefault(currentSession, "server", "172.16.0.158");
|
||||
|
||||
addClassName(LumoUtility.Padding.MEDIUM);
|
||||
add(new ViewToolbar("Main"));
|
||||
add(new Div("Please select a view from the menu on the left."));
|
||||
|
||||
String jsCode = """
|
||||
var inactivityTime = function () {
|
||||
var time;
|
||||
window.onload = resetTimer;
|
||||
document.onmousemove = resetTimer;
|
||||
document.onkeypress = resetTimer;
|
||||
document.onclick = resetTimer;
|
||||
document.onscroll = resetTimer;
|
||||
|
||||
function logout() {
|
||||
$0.$server.logout(); // Server-Logout via RPC
|
||||
}
|
||||
|
||||
function resetTimer() {
|
||||
clearTimeout(time);
|
||||
time = setTimeout(logout, 5 * 60 * 1000); // 5 Minute Inaktivität
|
||||
}
|
||||
};
|
||||
inactivityTime();
|
||||
""";
|
||||
|
||||
UI.getCurrent().getPage().executeJs(jsCode, this);
|
||||
|
||||
var horizontalLayout = new HorizontalLayout();
|
||||
add(horizontalLayout);
|
||||
|
||||
Button startBtn = new Button("Start", event -> startup());
|
||||
startBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
horizontalLayout.add(startBtn);
|
||||
|
||||
Button logoutBtn = new Button("Logout", event -> logout());
|
||||
logoutBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
horizontalLayout.add(logoutBtn);
|
||||
|
||||
webView = new IFrame();
|
||||
webView.setSizeFull(); // IFrame auf volle Größe setzen
|
||||
webView.getElement().setAttribute("frameborder", "0"); // Optional: Rahmen entfernen
|
||||
|
||||
// Layout konfigurieren
|
||||
setSizeFull(); // Vertikales Layout auf volle Größe setzen
|
||||
add(webView);
|
||||
|
||||
addAttachListener(event -> {
|
||||
ui = event.getUI();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the main view.
|
||||
*/
|
||||
public static void showMainView() {
|
||||
UI.getCurrent().navigate(MainView.class);
|
||||
@Override
|
||||
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
|
||||
String user = (String) VaadinService.getCurrentRequest().getWrappedSession().getAttribute("user");
|
||||
if (user == null) {
|
||||
beforeEnterEvent.rerouteTo("login");
|
||||
}
|
||||
}
|
||||
|
||||
private void startup() {
|
||||
UI ui = UI.getCurrent();
|
||||
|
||||
var dialog = showWaitDialog("Der Emulator wird gestartet, bitte warten...");
|
||||
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
shutdown();
|
||||
|
||||
createContainer();
|
||||
|
||||
startContainer();
|
||||
|
||||
waitContainerStart();
|
||||
|
||||
downloadApp();
|
||||
|
||||
installApp();
|
||||
|
||||
} catch (Exception ex) {
|
||||
// Logging, Fehlerbehandlung …
|
||||
} finally {
|
||||
// 4) UI-update sicher aus Nicht-UI-Thread heraus
|
||||
ui.access(() -> {
|
||||
dialog.close();
|
||||
|
||||
refreshWebView();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void shutdown() {
|
||||
stopContainer();
|
||||
|
||||
deleteContainer();
|
||||
}
|
||||
|
||||
private void refreshWebView() {
|
||||
webView.setSrc("http://" + server + ":6080/?autoconnect=true");
|
||||
webView.reload();
|
||||
}
|
||||
|
||||
private void downloadApp() {
|
||||
try {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
String jsonPayload = """
|
||||
{
|
||||
"Cmd": ["curl", "-O", "https://www.appcreation.de/download/sb.apk"],
|
||||
"AttachStdout": true,
|
||||
"AttachStderr": true
|
||||
}""";
|
||||
|
||||
// HTTP-Request erstellen
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/exec"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||
.build();
|
||||
|
||||
// Anfrage senden und Antwort verarbeiten
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("HTTP-Response-Code: " + response.statusCode());
|
||||
System.out.println("Response-Body: " + response.body());
|
||||
|
||||
var execId = ExecResponse.parse(response.body());
|
||||
|
||||
exec(execId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void installApp() {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
String jsonPayload = """
|
||||
{
|
||||
"Cmd": ["adb", "install", "sb.apk"],
|
||||
"AttachStdout": true,
|
||||
"AttachStderr": true
|
||||
}""";
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/exec"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||
.build();
|
||||
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("HTTP-Response-Code: " + response.statusCode());
|
||||
System.out.println("Response-Body: " + response.body());
|
||||
|
||||
var execId = ExecResponse.parse(response.body());
|
||||
|
||||
exec(execId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String exec(String execId) {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://" + server + ":2375/exec/" + execId + "/start"))
|
||||
.POST(HttpRequest.BodyPublishers.noBody())
|
||||
.version(HttpClient.Version.HTTP_1_1)
|
||||
.build();
|
||||
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("HTTP-Response-Code: " + response.statusCode()); // Statuscode ausgeben
|
||||
System.out.println("Response-Body: " + response.body()); // Antwort-Body ausgeben
|
||||
|
||||
return response.body();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(); // Fehler behandeln
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void startContainer() {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/start"))
|
||||
.POST(HttpRequest.BodyPublishers.noBody()) // Kein Body wird gesendet
|
||||
.build();
|
||||
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("HTTP-Response-Code: " + response.statusCode()); // Statuscode ausgeben
|
||||
System.out.println("Response-Body: " + response.body()); // Antwort-Body ausgeben
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(); // Fehler behandeln
|
||||
}
|
||||
}
|
||||
|
||||
private void waitContainerStart() {
|
||||
boolean endLoop = false;
|
||||
|
||||
do {
|
||||
try {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
String jsonPayload = """
|
||||
{
|
||||
"Cmd": ["cat", "device_status"],
|
||||
"AttachStdout": true,
|
||||
"AttachStderr": true
|
||||
}""";
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/exec"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("HTTP-Response-Code: " + response.statusCode());
|
||||
System.out.println("Response-Body: " + response.body());
|
||||
|
||||
var execId = ExecResponse.parse(response.body());
|
||||
|
||||
var execResponse = exec(execId);
|
||||
if (execResponse != null) {
|
||||
var trimmedResponse = execResponse.trim();
|
||||
|
||||
if (trimmedResponse.equals("READY")) {
|
||||
endLoop = true;
|
||||
} else {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} while (!endLoop);
|
||||
}
|
||||
|
||||
private void createContainer() {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
// JSON-Payload für die Anfrage
|
||||
String jsonPayload = """
|
||||
{
|
||||
"Image": "budtmo/docker-android:emulator_11.0",
|
||||
"Env": [
|
||||
"EMULATOR_DEVICE=Samsung Galaxy S10",
|
||||
"WEB_VNC=true"
|
||||
],
|
||||
"HostConfig": {
|
||||
"PortBindings": {
|
||||
"6080/tcp": [
|
||||
{ "HostPort": "6080" }
|
||||
]
|
||||
},
|
||||
"Devices": [
|
||||
{
|
||||
"PathOnHost": "/dev/kvm",
|
||||
"PathInContainer": "/dev/kvm",
|
||||
"CgroupPermissions": "rwm"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ExposedPorts": {
|
||||
"6080/tcp": {}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
// HTTP-Request erstellen
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://" + server + ":2375/containers/create?name=android-container-" + username))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||
.build();
|
||||
|
||||
// Anfrage senden und Antwort verarbeiten
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("HTTP-Response-Code: " + response.statusCode());
|
||||
System.out.println("Response-Body: " + response.body());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopContainer() {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/stop"))
|
||||
.POST(HttpRequest.BodyPublishers.noBody()) // Kein Body wird gesendet
|
||||
.build();
|
||||
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("HTTP-Response-Code: " + response.statusCode()); // Statuscode ausgeben
|
||||
System.out.println("Response-Body: " + response.body()); // Antwort-Body ausgeben
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(); // Fehler behandeln
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteContainer() {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "?force=true"))
|
||||
.DELETE() // Kein Body wird gesendet
|
||||
.build();
|
||||
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("HTTP-Response-Code: " + response.statusCode()); // Statuscode ausgeben
|
||||
System.out.println("Response-Body: " + response.body()); // Antwort-Body ausgeben
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(); // Fehler behandeln
|
||||
}
|
||||
}
|
||||
|
||||
private Dialog showWaitDialog(String message) {
|
||||
Dialog dialog = new Dialog();
|
||||
dialog.setHeaderTitle("Bitte warten");
|
||||
dialog.setModal(true);
|
||||
dialog.setCloseOnOutsideClick(false);
|
||||
|
||||
VerticalLayout dialogLayout = new VerticalLayout(
|
||||
new Paragraph(message)
|
||||
);
|
||||
dialog.add(dialogLayout);
|
||||
|
||||
dialog.open();
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@ClientCallable
|
||||
public void logout() {
|
||||
UI ui = UI.getCurrent();
|
||||
var vaadinSession = VaadinService.getCurrentRequest().getWrappedSession();
|
||||
|
||||
var dialog = showWaitDialog("Sie werden abgemeldet. Bitte warten...");
|
||||
dialog.open();
|
||||
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
shutdown();
|
||||
} catch (Exception ex) {
|
||||
// Logging, Fehlerbehandlung …
|
||||
} finally {
|
||||
// 4) UI-update sicher aus Nicht-UI-Thread heraus
|
||||
ui.access(() -> {
|
||||
dialog.close();
|
||||
|
||||
vaadinSession.invalidate();
|
||||
|
||||
// Navigiere den Benutzer zur Login-Seite
|
||||
ui.navigate("");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* This package contains reusable or cross-cutting view-related classes.
|
||||
*/
|
||||
@NullMarked
|
||||
package de.assecutor.emulatorstation.base.ui.view;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.assecutor.emulatorstation.pojo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonKey;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class ExecResponse {
|
||||
@com.fasterxml.jackson.annotation.JsonProperty("Id")
|
||||
private String id;
|
||||
|
||||
public static String parse(String json) throws JsonProcessingException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
var value = objectMapper.readValue(json, ExecResponse.class);
|
||||
|
||||
return value.getId();
|
||||
}
|
||||
|
||||
// Getter und Setter
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package de.assecutor.emulatorstation.taskmanagement.domain;
|
||||
|
||||
import de.assecutor.emulatorstation.base.domain.AbstractEntity;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
@Table(name = "task")
|
||||
public class Task extends AbstractEntity<Long> {
|
||||
|
||||
public static final int DESCRIPTION_MAX_LENGTH = 255;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "task_id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "description", nullable = false, length = DESCRIPTION_MAX_LENGTH)
|
||||
@Size(max = DESCRIPTION_MAX_LENGTH)
|
||||
private String description;
|
||||
|
||||
@Column(name = "creation_date", nullable = false)
|
||||
private Instant creationDate;
|
||||
|
||||
@Column(name = "due_date")
|
||||
@Nullable
|
||||
private LocalDate dueDate;
|
||||
|
||||
@Override
|
||||
public @Nullable Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Instant getCreationDate() {
|
||||
return creationDate;
|
||||
}
|
||||
|
||||
public void setCreationDate(Instant creationDate) {
|
||||
this.creationDate = creationDate;
|
||||
}
|
||||
|
||||
public @Nullable LocalDate getDueDate() {
|
||||
return dueDate;
|
||||
}
|
||||
|
||||
public void setDueDate(@Nullable LocalDate dueDate) {
|
||||
this.dueDate = dueDate;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package de.assecutor.emulatorstation.taskmanagement.domain;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface TaskRepository extends JpaRepository<Task, Long>, JpaSpecificationExecutor<Task> {
|
||||
|
||||
// If you don't need a total row count, Slice is better than Page.
|
||||
Slice<Task> findAllBy(Pageable pageable);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* This package contains the domain model of the Task Management sample feature.
|
||||
* <p>
|
||||
* You can add as many domain model artifacts (such as entities, value objects, repositories, domain events, and domain
|
||||
* services) to this package, as long as they belong to the same feature.
|
||||
* </p>
|
||||
* <p>
|
||||
* If you have domain classes that are re-usable across multiple features, add them to the {@code base.domain} package.
|
||||
* </p>
|
||||
*/
|
||||
@NullMarked
|
||||
package de.assecutor.emulatorstation.taskmanagement.domain;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* This is a feature package for the Task Management sample feature. Its purpose is to demonstrate how you typically
|
||||
* structure Vaadin business applications, and how the different building blocks interact.
|
||||
* <p>
|
||||
* A feature package represents a self-contained unit of functionality, including UI components, business logic, and
|
||||
* data access. It could be a subdomain or bounded context (e.g., "Billing"), a specific use case (e.g., "User
|
||||
* Registration"), or even a complex UI view (e.g., "Dashboard").
|
||||
* </p>
|
||||
* <p>
|
||||
* If your application is very small, you may not need dedicated feature packages. In that case, move the subpackages
|
||||
* directly to the application package.
|
||||
* </p>
|
||||
*/
|
||||
package de.assecutor.emulatorstation.taskmanagement;
|
||||
// TODO Remove this package once you have added real features
|
||||
@@ -1,43 +0,0 @@
|
||||
package de.assecutor.emulatorstation.taskmanagement.service;
|
||||
|
||||
import de.assecutor.emulatorstation.taskmanagement.domain.Task;
|
||||
import de.assecutor.emulatorstation.taskmanagement.domain.TaskRepository;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public class TaskService {
|
||||
|
||||
private final TaskRepository taskRepository;
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
TaskService(TaskRepository taskRepository, Clock clock) {
|
||||
this.taskRepository = taskRepository;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
public void createTask(String description, @Nullable LocalDate dueDate) {
|
||||
if ("fail".equals(description)) {
|
||||
throw new RuntimeException("This is for testing the error handler");
|
||||
}
|
||||
var task = new Task();
|
||||
task.setDescription(description);
|
||||
task.setCreationDate(clock.instant());
|
||||
task.setDueDate(dueDate);
|
||||
taskRepository.saveAndFlush(task);
|
||||
}
|
||||
|
||||
public List<Task> list(Pageable pageable) {
|
||||
return taskRepository.findAllBy(pageable).toList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* This package contains the application services of the Task Management sample feature.
|
||||
* <p>
|
||||
* You can add as many application services as you want to this package, as long as they belong to the same feature. If
|
||||
* any of them use Data Transfer Objects (DTO), you can add them here as well.
|
||||
* </p>
|
||||
*/
|
||||
@NullMarked
|
||||
package de.assecutor.emulatorstation.taskmanagement.service;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -1,12 +1,9 @@
|
||||
package de.assecutor.emulatorstation.taskmanagement.ui.view;
|
||||
|
||||
import de.assecutor.emulatorstation.base.ui.component.ViewToolbar;
|
||||
import de.assecutor.emulatorstation.taskmanagement.domain.Task;
|
||||
import de.assecutor.emulatorstation.taskmanagement.service.TaskService;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.datepicker.DatePicker;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.html.Main;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.notification.NotificationVariant;
|
||||
@@ -18,32 +15,20 @@ import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.vaadin.flow.spring.data.VaadinSpringDataHelpers.toSpringPageRequest;
|
||||
|
||||
@Route("task-list")
|
||||
@PageTitle("Task List")
|
||||
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Task List")
|
||||
@PermitAll // When security is enabled, allow all authenticated users
|
||||
public class TaskListView extends Main {
|
||||
|
||||
private final TaskService taskService;
|
||||
|
||||
final TextField description;
|
||||
final DatePicker dueDate;
|
||||
final Button createBtn;
|
||||
final Grid<Task> taskGrid;
|
||||
|
||||
public TaskListView(TaskService taskService, Clock clock) {
|
||||
this.taskService = taskService;
|
||||
|
||||
public TaskListView() {
|
||||
description = new TextField();
|
||||
description.setPlaceholder("What do you want to do?");
|
||||
description.setAriaLabel("Task description");
|
||||
description.setMaxLength(Task.DESCRIPTION_MAX_LENGTH);
|
||||
description.setMinWidth("20em");
|
||||
|
||||
dueDate = new DatePicker();
|
||||
@@ -53,33 +38,13 @@ public class TaskListView extends Main {
|
||||
createBtn = new Button("Create", event -> createTask());
|
||||
createBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
var dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withZone(clock.getZone())
|
||||
.withLocale(getLocale());
|
||||
var dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(getLocale());
|
||||
|
||||
taskGrid = new Grid<>();
|
||||
taskGrid.setItems(query -> taskService.list(toSpringPageRequest(query)).stream());
|
||||
taskGrid.addColumn(Task::getDescription).setHeader("Description");
|
||||
taskGrid.addColumn(task -> Optional.ofNullable(task.getDueDate()).map(dateFormatter::format).orElse("Never"))
|
||||
.setHeader("Due Date");
|
||||
taskGrid.addColumn(task -> dateTimeFormatter.format(task.getCreationDate())).setHeader("Creation Date");
|
||||
taskGrid.setSizeFull();
|
||||
|
||||
setSizeFull();
|
||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
||||
|
||||
add(new ViewToolbar("Task List", ViewToolbar.group(description, dueDate, createBtn)));
|
||||
add(taskGrid);
|
||||
}
|
||||
|
||||
private void createTask() {
|
||||
taskService.createTask(description.getValue(), dueDate.getValue());
|
||||
taskGrid.getDataProvider().refreshAll();
|
||||
description.clear();
|
||||
dueDate.clear();
|
||||
Notification.show("Task added", 3000, Notification.Position.BOTTOM_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* This package contains the views of the Task Management sample feature.
|
||||
* <p>
|
||||
* You can add as many views as you want to this package, as long as they belong to the same feature.
|
||||
* </p>
|
||||
* <p>
|
||||
* For smaller UI-components, consider creating a separate {@code ui.component} package. If the components are re-usable
|
||||
* across multiple features, add them to the {@code base.ui.component} package.
|
||||
* </p>
|
||||
*/
|
||||
@NullMarked
|
||||
package de.assecutor.emulatorstation.taskmanagement.ui.view;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
20
src/main/java/util/PreferencesKeyValueStore.java
Normal file
20
src/main/java/util/PreferencesKeyValueStore.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package util;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
@Service
|
||||
public class PreferencesKeyValueStore {
|
||||
|
||||
private final Preferences prefs = Preferences.userRoot().node("emulatorstation");
|
||||
|
||||
public void put(String key, String value) {
|
||||
prefs.put(key, value);
|
||||
}
|
||||
|
||||
public Optional<String> get(String key) {
|
||||
return Optional.ofNullable(prefs.get(key, null));
|
||||
}
|
||||
}
|
||||
19
src/main/java/util/Util.java
Normal file
19
src/main/java/util/Util.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package util;
|
||||
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.applayout.AppLayout;
|
||||
import com.vaadin.flow.server.VaadinSession;
|
||||
import com.vaadin.flow.server.WrappedSession;
|
||||
|
||||
|
||||
public class Util {
|
||||
public static String getSessionAttributeWithDefault(WrappedSession currentSession, String key, String defaultValue) {
|
||||
var result = defaultValue;
|
||||
|
||||
try {
|
||||
result = currentSession.getAttribute(key).toString();
|
||||
} catch (Exception e) {}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
10
src/main/resources/MyVaadinServiceInitListener.java
Normal file
10
src/main/resources/MyVaadinServiceInitListener.java
Normal file
@@ -0,0 +1,10 @@
|
||||
import com.vaadin.flow.server.ServiceInitEvent;
|
||||
import com.vaadin.flow.server.VaadinServiceInitListener;
|
||||
|
||||
public class MyVaadinServiceInitListener implements VaadinServiceInitListener {
|
||||
@Override
|
||||
public void serviceInit(ServiceInitEvent event) {
|
||||
// Setze das global für den Service gültige Heartbeat-Interval
|
||||
event.getSource().setHeartbeatInterval(300); // 5 Minuten (300 Sekunden)
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,6 @@ spring.jpa.open-in-view=false
|
||||
|
||||
# Initialize the JPA Entity Manager before considering data.sql so that the EM can create the schema and data.sql contain data
|
||||
spring.jpa.defer-datasource-initialization = true
|
||||
|
||||
server.servlet.session.timeout=300s
|
||||
vaadin.heartbeatInterval=300
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package de.assecutor.emulatorstation;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClasses;
|
||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
|
||||
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
|
||||
import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
|
||||
|
||||
class ArchitectureTest {
|
||||
|
||||
static final String BASE_PACKAGE = "de.assecutor.emulatorstation";
|
||||
|
||||
private final JavaClasses importedClasses = new ClassFileImporter().importPackages(BASE_PACKAGE);
|
||||
|
||||
// TODO Add your own rules and remove those that don't apply to your project
|
||||
|
||||
@Test
|
||||
void domain_model_should_not_depend_on_application_services() {
|
||||
noClasses().that().resideInAPackage(BASE_PACKAGE + "..domain..").should().dependOnClassesThat()
|
||||
.resideInAPackage(BASE_PACKAGE + "..service..").check(importedClasses);
|
||||
}
|
||||
|
||||
@Test
|
||||
void domain_model_should_not_depend_on_the_user_interface() {
|
||||
noClasses().that().resideInAPackage(BASE_PACKAGE + "..domain..").should().dependOnClassesThat()
|
||||
.resideInAnyPackage(BASE_PACKAGE + "..ui..").check(importedClasses);
|
||||
}
|
||||
|
||||
@Test
|
||||
void repositories_should_only_be_used_by_application_services_and_other_domain_classes() {
|
||||
classes().that().areAssignableTo(Repository.class).should().onlyHaveDependentClassesThat()
|
||||
.resideInAnyPackage(BASE_PACKAGE + "..domain..", BASE_PACKAGE + "..service..").check(importedClasses);
|
||||
}
|
||||
|
||||
@Test
|
||||
void repositories_should_only_be_accessed_by_transactional_classes() {
|
||||
classes().that().areAssignableTo(Repository.class).should().onlyBeAccessed().byClassesThat()
|
||||
.areAnnotatedWith(Transactional.class).check(importedClasses);
|
||||
}
|
||||
|
||||
@Test
|
||||
void application_services_should_not_depend_on_the_user_interface() {
|
||||
noClasses().that().resideInAPackage(BASE_PACKAGE + "..service..").should().dependOnClassesThat()
|
||||
.resideInAnyPackage(BASE_PACKAGE + "..ui..").check(importedClasses);
|
||||
}
|
||||
|
||||
@Test
|
||||
void there_should_not_be_circular_dependencies_between_feature_packages() {
|
||||
slices().matching(BASE_PACKAGE + ".(*)..").should().beFreeOfCycles().check(importedClasses);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package de.assecutor.emulatorstation;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
|
||||
/**
|
||||
* Run this application class to start your application locally, using Testcontainers for all external services. You
|
||||
* have to configure the containers in {@link TestcontainersConfiguration}.
|
||||
*/
|
||||
public class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.from(Application::main).with(TestcontainersConfiguration.class).run(args);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package de.assecutor.emulatorstation;
|
||||
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
|
||||
@TestConfiguration(proxyBeanMethods = false)
|
||||
public class TestcontainersConfiguration {
|
||||
|
||||
// TODO Configure your Testcontainers here.
|
||||
// See https://docs.spring.io/spring-boot/reference/testing/testcontainers.html for details.
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package de.assecutor.emulatorstation.taskmanagement.service;
|
||||
|
||||
import de.assecutor.emulatorstation.TestcontainersConfiguration;
|
||||
import de.assecutor.emulatorstation.taskmanagement.domain.Task;
|
||||
import de.assecutor.emulatorstation.taskmanagement.domain.TaskRepository;
|
||||
import jakarta.validation.ValidationException;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
@Import(TestcontainersConfiguration.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
class TaskServiceIT {
|
||||
|
||||
@Autowired
|
||||
TaskService taskService;
|
||||
|
||||
@Autowired
|
||||
TaskRepository taskRepository;
|
||||
|
||||
@Autowired
|
||||
Clock clock;
|
||||
|
||||
@AfterEach
|
||||
void cleanUp() {
|
||||
taskRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tasks_are_stored_in_the_database_with_the_current_timestamp() {
|
||||
var now = clock.instant();
|
||||
var due = LocalDate.of(2025, 2, 7);
|
||||
taskService.createTask("Do this", due);
|
||||
assertThat(taskService.list(PageRequest.ofSize(1))).singleElement()
|
||||
.matches(task -> task.getDescription().equals("Do this") && due.equals(task.getDueDate())
|
||||
&& task.getCreationDate().isAfter(now));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tasks_are_validated_before_they_are_stored() {
|
||||
assertThatThrownBy(() -> taskService.createTask("X".repeat(Task.DESCRIPTION_MAX_LENGTH + 1), null))
|
||||
.isInstanceOf(ValidationException.class);
|
||||
assertThat(taskRepository.count()).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user