diff --git a/chat/src/main/java/rtgre/chat/ChatController.java b/chat/src/main/java/rtgre/chat/ChatController.java index 4ea8062..5e8e0cc 100644 --- a/chat/src/main/java/rtgre/chat/ChatController.java +++ b/chat/src/main/java/rtgre/chat/ChatController.java @@ -3,6 +3,7 @@ package rtgre.chat; import javafx.application.Platform; import javafx.beans.Observable; import javafx.beans.binding.Bindings; +import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.embed.swing.SwingFXUtils; @@ -22,11 +23,13 @@ import javafx.stage.Stage; import net.synedra.validatorfx.Check; import net.synedra.validatorfx.TooltipWrapper; import net.synedra.validatorfx.Validator; +import org.json.JSONObject; import rtgre.chat.graphisme.ContactListViewCell; import rtgre.chat.graphisme.PostListViewCell; import rtgre.chat.net.ChatClient; import rtgre.modeles.*; +import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -96,6 +99,7 @@ public class ChatController implements Initializable { avatarMenuItem.setOnAction(this::handleAvatarChange); avatarImageView.setOnMouseClicked(this::handleAvatarChange); sendButton.setOnAction(this::onActionSend); + messageTextField.setOnAction(this::onActionSend); initContactListView(); initPostListView(); @@ -108,6 +112,10 @@ public class ChatController implements Initializable { .decorates(loginTextField) .immediate(); + ObservableValue canSendCondition = connectionButton.selectedProperty().not() + .or(contactsListView.getSelectionModel().selectedItemProperty().isNull()); + sendButton.disableProperty().bind(canSendCondition); + messageTextField.disableProperty().bind(canSendCondition); /* /!\ Set-up d'environnement de test /!\ */ /* -------------------------------------- */ @@ -120,7 +128,9 @@ public class ChatController implements Initializable { String login = getSelectedContactLogin(); if (login != null) { Message message = new Message(login, messageTextField.getText()); - LOGGER.info(message.toString()); + LOGGER.info("Sending " + message); + client.sendMessageEvent(message); + this.messageTextField.setText(""); } } @@ -149,17 +159,22 @@ public class ChatController implements Initializable { contactMap.put(this.contact.getLogin(), this.contact); LOGGER.info("Nouveau contact : " + contact); LOGGER.info(contactMap.toString()); - Matcher matcher = hostPortPattern.matcher("localhost:2024"); + Matcher matcher = hostPortPattern.matcher(hostComboBox.getValue()); matcher.matches(); String host = matcher.group(1); int port = (matcher.group(2) != null) ? Integer.parseInt(matcher.group(2)) : 2024; try { + LOGGER.info(host + ":" + port); this.client = new ChatClient(host, port, this); initContactListView(); initPostListView(); clearLists(); contactMap.add(this.contact); this.contact.setConnected(true); + + client.sendAuthEvent(contact); + client.sendEvent(new rtgre.modeles.Event(rtgre.modeles.Event.LIST_CONTACTS, new JSONObject())); + initContactListView(); initPostListView(); this.statusLabel.setText("Connected to %s@%s:%s".formatted(this.contact.getLogin(), host, port)); @@ -168,9 +183,11 @@ public class ChatController implements Initializable { connectionButton.setSelected(false); } } else if (!connectionButton.isSelected()) { + this.client.sendQuitEvent(); clearLists(); - this.client.close(); - this.contact.setConnected(false); + if (this.client.isConnected()) { + this.contact.setConnected(false); + } statusLabel.setText("not connected to " + hostComboBox.getValue()); } @@ -260,8 +277,55 @@ public class ChatController implements Initializable { if (contactSelected != null) { LOGGER.info("Clic sur " + contactSelected); } - Post postSys = new Post("system", contactSelected.getLogin(), "Bienvenue dans la discussion avec " + contactSelected.getLogin()); + Post postSys = new Post("system", loginTextField.getText(), "Bienvenue dans la discussion avec " + contactSelected.getLogin()); + postsObservableList.clear(); postsObservableList.add(postSys); + client.sendListPostEvent(0, contactSelected.getLogin()); postListView.refresh(); } + + public void handleEvent(rtgre.modeles.Event event) { + LOGGER.info("Received new event! : " + event); + LOGGER.info(event.getType()); + if (event.getType().equals("CONT")) { + handleContEvent(event.getContent()); + } else if (event.getType().equals(rtgre.modeles.Event.POST)) { + handlePostEvent(event.getContent()); + } else { + LOGGER.warning("Unhandled event type: " + event.getType()); + this.client.close(); + } + } + + private void handlePostEvent(JSONObject content) { + System.out.println(content.getString("to").equals(((Contact) contactsListView.getSelectionModel().getSelectedItem()).getLogin())); + if (content.getString("to").equals(((Contact) contactsListView.getSelectionModel().getSelectedItem()).getLogin()) || + content.getString("from").equals(loginTextField.getText())) { + postVector.add(Post.fromJson(content)); + postsObservableList.add(Post.fromJson(content)); + postListView.refresh(); + + } + } + + private void handleContEvent(JSONObject content) { + Contact contact = contactMap.getContact(content.getString("login")); + if (contact != null) { + LOGGER.info(contactMap.toString()); + contactMap.getContact(content.getString("login")).setConnected(content.getBoolean("connected")); + contactsListView.refresh(); + LOGGER.info(contactMap.toString()); + } else { + LOGGER.info(contactMap.toString()); + Contact user = Contact.fromJSON( + content, + new File("chat/src/main/resources/rtgre/chat/avatars.png") + ); + System.out.println(user.getAvatar()); + contactMap.add(user); + contactObservableList.add(user); + LOGGER.info(contactMap.toString()); + } + } + } \ No newline at end of file diff --git a/chat/src/main/java/rtgre/chat/net/ChatClient.java b/chat/src/main/java/rtgre/chat/net/ChatClient.java index 009ffb6..aed444e 100644 --- a/chat/src/main/java/rtgre/chat/net/ChatClient.java +++ b/chat/src/main/java/rtgre/chat/net/ChatClient.java @@ -1,8 +1,15 @@ package rtgre.chat.net; +import javafx.application.Platform; +import org.json.JSONObject; import rtgre.chat.ChatController; +import rtgre.modeles.Contact; +import rtgre.modeles.Event; +import rtgre.modeles.Message; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.util.logging.Logger; import static rtgre.chat.ChatApplication.LOGGER; @@ -26,6 +33,47 @@ public class ChatClient extends ClientTCP { this.listener = listener; } + + + public void sendEvent(Event event) { + connected = true; + try { + String message = event.toJson(); + if (message == null) { // fin du flux stdIn + message = END_MESSAGE; + } + System.out.println(BLUE + "Envoi: " + message + RST); + this.send(message); + if (END_MESSAGE.equals(message)) { + connected = false; + } + } catch (IOException e) { + LOGGER.severe(e.toString()); + connected = false; + } + } + + public void sendAuthEvent(Contact contact) { + Event authEvent = new Event(Event.AUTH, new JSONObject().put("login", contact.getLogin())); + sendEvent(authEvent); + } + + public void sendListPostEvent(long since, String select) { + Event listPostEvent = new Event( + Event.LIST_POSTS, + new JSONObject() + .put("since", since) + .put("select", select) + ); + sendEvent(listPostEvent); + } + + public void sendQuitEvent() { + Event quitEvent = new Event(Event.QUIT, new JSONObject()); + sendEvent(quitEvent); + } + + @Override public void receiveLoop() { LOGGER.info(RED + "Boucle de réception de messages..." + RST); @@ -34,6 +82,9 @@ public class ChatClient extends ClientTCP { String message = this.receive(); LOGGER.info(RED + "Réception: " + message + RST); LOGGER.info(RED + message + RST); + if (listener != null) { + Platform.runLater(() -> listener.handleEvent(Event.fromJson(message))); + } } } catch (IOException e) { LOGGER.severe("[%s] %s".formatted(ipPort, e)); @@ -50,4 +101,8 @@ public class ChatClient extends ClientTCP { public ChatController getListener() { return listener; } + + public void sendMessageEvent(Message msg) { + sendEvent(new Event("MESG", msg.toJsonObject())); + } } diff --git a/chat/src/main/java/rtgre/modeles/Contact.java b/chat/src/main/java/rtgre/modeles/Contact.java index 549fefa..bb9ff5a 100644 --- a/chat/src/main/java/rtgre/modeles/Contact.java +++ b/chat/src/main/java/rtgre/modeles/Contact.java @@ -1,6 +1,8 @@ package rtgre.modeles; +import org.json.JSONObject; + import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; @@ -101,6 +103,20 @@ public class Contact { return 0; } + public JSONObject toJsonObject() { + return new JSONObject() + .put("login", this.login) + .put("connected", this.connected); + } + + public String toJson() { + return toJsonObject().toString(); + } + + public static Contact fromJSON(JSONObject jsonObject, File banque_avatars) { + return new Contact(jsonObject.getString("login"), jsonObject.getBoolean("connected"), banque_avatars); + } + public static BufferedImage avatarFromLogin(File fichier, String login) throws IOException { /** * Renvoie une sous-image en fonction d'une banque d'image et d'un login. diff --git a/chat/src/main/java/rtgre/modeles/ContactMap.java b/chat/src/main/java/rtgre/modeles/ContactMap.java index f44bcc0..5924158 100644 --- a/chat/src/main/java/rtgre/modeles/ContactMap.java +++ b/chat/src/main/java/rtgre/modeles/ContactMap.java @@ -9,4 +9,16 @@ public class ContactMap extends TreeMap { public Contact getContact(String login) { return this.get(login); } + + public void loadDefaultContacts() { + this.put("mickey", new Contact("mickey", null)); + this.put("minnie", new Contact("minnie", null)); + this.put("dingo", new Contact("dingo", null)); + this.put("riri", new Contact("riri", null)); + this.put("fifi", new Contact("fifi", null)); + this.put("loulou", new Contact("loulou", null)); + this.put("donald", new Contact("donald", null)); + this.put("daisy", new Contact("daisy", null)); + this.put("picsou", new Contact("picsou", null)); + } } diff --git a/chat/src/main/java/rtgre/modeles/Event.java b/chat/src/main/java/rtgre/modeles/Event.java new file mode 100644 index 0000000..9198f5e --- /dev/null +++ b/chat/src/main/java/rtgre/modeles/Event.java @@ -0,0 +1,52 @@ +package rtgre.modeles; + +import org.json.JSONException; +import org.json.JSONObject; + +public class Event { + public static final String AUTH = "AUTH"; + public static final String QUIT = "QUIT"; + public static final String MESG = "MESG"; + public static final String JOIN = "JOIN"; + public static final String POST = "POST"; + public static final String CONT = "CONT"; + public static final String LIST_CONTACTS = "LSTC"; + public static final String LIST_POSTS = "LSTP"; + public static final String SYSTEM = "SYST"; + private final String type; + private final JSONObject content; + + public String getType() { + return type; + } + + public JSONObject getContent() { + return content; + } + + public Event(String type, JSONObject content) { + this.type = type; + this.content = content; + } + + @Override + public String toString() { + return "Event{type=" + type + ", content=" + content.toString() + "}"; + } + + public JSONObject toJsonObject() { + return new JSONObject().put("type", type).put("content", content); + } + + public String toJson() { + return toJsonObject().toString(); + } + + public static Event fromJson(String json) throws JSONException { + JSONObject jsonObject = new JSONObject(json); + String type = jsonObject.getString("type"); + JSONObject content = jsonObject.getJSONObject("content"); + return new Event(type, content); + } +} + diff --git a/chat/src/main/java/rtgre/server/ChatServer.java b/chat/src/main/java/rtgre/server/ChatServer.java index 3665663..8a85ebd 100644 --- a/chat/src/main/java/rtgre/server/ChatServer.java +++ b/chat/src/main/java/rtgre/server/ChatServer.java @@ -1,5 +1,10 @@ package rtgre.server; +import org.json.JSONException; +import org.json.JSONObject; +import rtgre.chat.net.ChatClient; +import rtgre.modeles.*; + import java.io.*; import java.net.ServerSocket; import java.net.Socket; @@ -17,6 +22,8 @@ public class ChatServer { private static final Logger LOGGER = Logger.getLogger(ChatServer.class.getCanonicalName()); private Vector clientList; + private PostVector postVector; + private ContactMap contactMap; static { try { @@ -31,6 +38,7 @@ public class ChatServer { public static void main(String[] args) throws IOException { ChatServer server = new ChatServer(2024); + daisyConnect(); server.acceptClients(); } @@ -43,6 +51,9 @@ public class ChatServer { passiveSock = new ServerSocket(port); LOGGER.info("Serveur en écoute " + passiveSock); clientList = new Vector<>(); + contactMap = new ContactMap(); + postVector = new PostVector(); + contactMap.loadDefaultContacts(); } public void close() throws IOException { @@ -93,7 +104,7 @@ public class ChatServer { */ private void handleNewClient(Socket sock) throws IOException { ChatClientHandler client = new ChatClientHandler(sock); - Thread clientLoop = new Thread(client::echoLoop); + Thread clientLoop = new Thread(client::eventReceiveLoop); clientLoop.start(); clientList.add(client); LOGGER.fine("Ajout du client [%s] dans la liste (%d clients connectés)" @@ -101,6 +112,45 @@ public class ChatServer { //client.echoLoop(); } + public ChatClientHandler findClient(Contact contact) { + for (ChatClientHandler user: clientList) { + if (user.user.equals(contact)) { + return user; + } + } + return null; + } + + public void sendEventToContact(Contact contact, Event event) { + ChatClientHandler user = findClient(contact); + if (!(user == null)) { + try { + user.send(event.toJson()); + } catch (Exception e) { + LOGGER.warning("!!Erreur de l'envoi d'Event à %s, fermeture de la connexion".formatted(user.user.getLogin())); + user.close(); + } + } + } + + public void sendEventToAllContacts(Event event) { + for (Contact contact: contactMap.values()) { + if (contact.isConnected()) { + sendEventToContact(contact, event); + } + } + } + + public ContactMap getContactMap() { + return contactMap; + } + + /** Temporaire : connecte daisy pour test */ + public static void daisyConnect() throws IOException { + ChatClient client = new ChatClient("localhost", 2024, null); + client.sendAuthEvent(new Contact("daisy", null)); + } + private class ChatClientHandler { public static final String END_MESSAGE = "fin"; /** @@ -120,6 +170,8 @@ public class ChatServer { */ private String ipPort; + private Contact user; + /** * Initialise les attributs {@link #sock} (socket connecté au client), * {@link #out} (flux de caractères UTF-8 en sortie) et @@ -160,6 +212,123 @@ public class ChatServer { close(); } + public void eventReceiveLoop() { + try { + String message = null; + while (!END_MESSAGE.equals(message)) { + message = in.readLine(); + if (message == null) { + break; + } + LOGGER.info("[%s] Réception de : %s".formatted(ipPort, message)); + try { + if (!handleEvent(message)) { + break; + } + } catch (Exception e) { + LOGGER.severe(e.getMessage()); + break; + } + } + } catch (IOException e) { + LOGGER.severe("[%s] %s".formatted(ipPort, e)); + } + close(); + } + + private boolean handleEvent(String message) throws JSONException, IllegalStateException { + Event event = Event.fromJson(message); + if (event.getType().equals(Event.AUTH)) { + doLogin(event.getContent()); + LOGGER.finest("Login successful"); + return true; + } else if (event.getType().equals(Event.LIST_CONTACTS)) { + doListContact(event.getContent()); + LOGGER.finest("Sending contacts"); + return true; + } else if (event.getType().equals(Event.MESG)) { + doMessage(event.getContent()); + LOGGER.info("Receiving message"); + return true; + } else if (event.getType().equals(Event.LIST_POSTS)) { + doListPost(event.getContent()); + LOGGER.info("Sending Posts"); + return true; + } else if (event.getType().equals(Event.QUIT)) { + LOGGER.info("Déconnexion"); + return false; + } else { + LOGGER.warning("Unhandled event type: " + event.getType()); + return false; + } + } + + private void doListPost(JSONObject content) throws JSONException, IllegalStateException { + + if (contactMap.getContact(user.getLogin()).isConnected()) { + if (!contactMap.containsKey(content.getString("select"))) { + throw new IllegalStateException(); + } + for (Post post: postVector.getPostsSince(content.getLong("since"))) { + if (post.getTo().equals(content.getString("select")) || + post.getFrom().equals(content.getString("select"))) { + sendEventToContact(contactMap.getContact(user.getLogin()), new Event(Event.POST, post.toJsonObject())); + } + } + } + } + + private void doMessage(JSONObject content) throws JSONException, IllegalStateException { + if (contactMap.getContact(user.getLogin()).isConnected()) { + if (content.getString("to").equals(user.getLogin()) || !contactMap.containsKey(content.getString("to"))) { + throw new IllegalStateException(); + } else { + Post post = new Post( + user.getLogin(), + Message.fromJson(content) + ); + Event postEvent = new Event("POST", post.toJsonObject()); + + sendEventToContact(contactMap.getContact(post.getFrom()), postEvent); + sendEventToContact(contactMap.getContact(post.getTo()), postEvent); + + postVector.add(post); + LOGGER.info("Fin de doMessage"); + } + } + } + + private void doListContact(JSONObject content) throws JSONException, IllegalStateException { + for (Contact contact: contactMap.values()) { + if (contactMap.getContact(user.getLogin()).isConnected()) { + try { + send(new Event(Event.CONT, contact.toJsonObject()).toJson()); + } catch (IOException e) { + throw new IllegalStateException(); + } + } + } + } + + private void doLogin(JSONObject content) { + String login = content.getString("login"); + if (login.isEmpty()) { + LOGGER.warning("Aucun login fourni"); + throw new JSONException("Aucun login fourni"); + } else if (!contactMap.containsKey(login)) { + LOGGER.warning("Login non-authorisé"); + throw new IllegalStateException("Login non-authorisé"); + } else { + LOGGER.info("Connexion de " + login); + contactMap.getContact(login).setConnected(true); + this.user = contactMap.getContact(login); + sendAllOtherClients( + findClient(contactMap.getContact(login)), + new Event("CONT", user.toJsonObject()).toJson() + ); + } + } + public void send(String message) throws IOException { LOGGER.finest("send: %s".formatted(message)); out.println(message); @@ -189,7 +358,7 @@ public class ChatServer { public String receive() throws IOException { String message = in.readLine(); - LOGGER.finest("receive: %s".formatted(message)); + LOGGER.info("receive: %s".formatted(message)); if (message == null) { throw new IOException("End of the stream has been reached"); } @@ -201,6 +370,8 @@ public class ChatServer { try { sock.close(); removeClient(this); + user.setConnected(false); + sendEventToAllContacts(new Event(Event.CONT, user.toJsonObject())); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/chat/src/main/resources/rtgre/chat/banque_avatars.png b/chat/src/main/resources/rtgre/chat/banque_avatars.png new file mode 100644 index 0000000..b5e2c99 Binary files /dev/null and b/chat/src/main/resources/rtgre/chat/banque_avatars.png differ diff --git a/chat/src/test/java/rtgre/modeles/ContactWithEventTest3.java b/chat/src/test/java/rtgre/modeles/ContactWithEventTest3.java new file mode 100644 index 0000000..a45d891 --- /dev/null +++ b/chat/src/test/java/rtgre/modeles/ContactWithEventTest3.java @@ -0,0 +1,110 @@ +package rtgre.modeles; + +import org.json.JSONObject; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** Tests unitaires du modèle de base de Contact (étape 1) */ + +class ContactWithEventTest3 { + + static Class classe = Contact.class; + static String module = "rtgre.modeles"; + + @DisplayName("01-Structure de la classe Contact") + @Nested + class StructureTest { + + static List constructeursSignatures; + static List methodesSignatures; + + @BeforeAll + static void init() { + Constructor[] constructeurs = classe.getConstructors(); + constructeursSignatures = Arrays.stream(constructeurs).map(Constructor::toString).collect(Collectors.toList()); + Method[] methodes = classe.getDeclaredMethods(); + methodesSignatures = Arrays.stream(methodes).map(Method::toString).collect(Collectors.toList()); + } + + static Stream methodesProvider3() { + return Stream.of( + arguments("toJsonObject", "public org.json.JSONObject %s.Contact.toJsonObject()"), + arguments("toJson", "public java.lang.String %s.Contact.toJson()"), + arguments("fromJSON", "public static %s.Contact %s.Contact.fromJSON(org.json.JSONObject,java.io.File)") + ); + } + // @Disabled("Jusqu'à ce que soit codé les events") + @DisplayName("Déclaration des méthodes (avec event et JSON)") + @ParameterizedTest + @MethodSource("methodesProvider3") + void testDeclarationMethodes3(String nom, String signature) { + Assertions.assertTrue(methodesSignatures.contains(String.format(signature, module, module)), + String.format("Méthode non déclarée : doit être %s\nalors que sont déclarés %s", + signature, methodesSignatures)); + } + + + + } + + + @DisplayName("07-Représentation JSON") + @Nested + class JSONTest { + + @Test + @DisplayName("JSONObject") + void TestToJSONObject() { + String erreur = "Représentation JSON erronée"; + Contact fifi = new Contact("fifi", true, (Image) null); + JSONObject json = fifi.toJsonObject(); + Assertions.assertTrue(json.has("login"), erreur); + Assertions.assertEquals("fifi", json.get("login"), erreur); + Assertions.assertTrue(json.has("connected"), erreur); + Assertions.assertEquals(true, json.get("connected"), erreur); + } + + @Test + @DisplayName("Sérialisation JSON") + void TestToJSON() { + String erreur = "Sérialisation de la représentation JSON erronée"; + Contact riri = new Contact("riri", true, (Image) null); + String json = riri.toJson(); + Assertions.assertTrue(json.contains("\"login\":\"riri\""), erreur); + Assertions.assertTrue(json.contains("\"connected\":true"), erreur); + } + + @Test + @DisplayName("Contact à partir d'un JSON") + void TestConstructeur() throws IOException { + String work_dir = System.getProperty("user.dir"); + Assertions.assertTrue(work_dir.endsWith("chat"), + "Le working dir doit être /chat/ et non : " + work_dir); + File f = new File("src/main/resources/rtgre/chat/banque_avatars.png"); + String json = "{\"login\":\"riri\",\"connected\":true}"; + Contact toto = Contact.fromJSON(new JSONObject(json), f); + Assertions.assertEquals("riri", toto.getLogin(), "Login erroné"); + Assertions.assertEquals(true, toto.isConnected(), "Connected erroné"); + Assertions.assertNotNull(toto.getAvatar(), "Avatar non chargé"); + } + + } + +} \ No newline at end of file diff --git a/chat/src/test/java/rtgre/modeles/EventTest.java b/chat/src/test/java/rtgre/modeles/EventTest.java new file mode 100644 index 0000000..18c9795 --- /dev/null +++ b/chat/src/test/java/rtgre/modeles/EventTest.java @@ -0,0 +1,223 @@ +package rtgre.modeles; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class EventTest { + + static Class classe = Event.class; + static String module = "rtgre.modeles"; + + // @Tag("01-mise_en_place_contact") + @DisplayName("01-Structure de Event") + @Nested + class StructureTest { + + static List constructeursSignatures; + static List methodesSignatures; + + @BeforeAll + static void init() { + Constructor[] constructeurs = classe.getConstructors(); + constructeursSignatures = Arrays.stream(constructeurs).map(Constructor::toString).collect(Collectors.toList()); + Method[] methodes = classe.getDeclaredMethods(); + methodesSignatures = Arrays.stream(methodes).map(Method::toString).collect(Collectors.toList()); + } + + static Stream constantesProvider() { + return Stream.of( + arguments("AUTH", "AUTH"), + arguments("QUIT", "QUIT"), + arguments("MESG", "MESG"), + arguments("JOIN", "JOIN"), + arguments("POST", "POST"), + arguments("CONT", "CONT"), + arguments("LIST_POSTS", "LSTP"), + arguments("LIST_CONTACTS", "LSTC"), + arguments("SYSTEM", "SYST") + ); + } + + @DisplayName("Déclaration des constantes") + @ParameterizedTest + @MethodSource("constantesProvider") + void testDeclarationConstantes(String constante, String value) throws NoSuchFieldException, IllegalAccessException { + Field field = classe.getField(constante); + Assertions.assertEquals(Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC, field.getModifiers(), + "Visibilité " + constante + " erronée"); + Assertions.assertEquals("java.lang.String", field.getType().getName(), + "Type " + constante + " erroné"); + Assertions.assertEquals(value, field.get(null), "Valeur " + constante + " erronée"); + } + + + static Stream attributsProvider() { + return Stream.of( + arguments("type", "java.lang.String", Modifier.PRIVATE | Modifier.FINAL), + arguments("content", "org.json.JSONObject", Modifier.PRIVATE | Modifier.FINAL) + ); + } + + @DisplayName("Déclaration des attributs") + @ParameterizedTest + @MethodSource("attributsProvider") + void testDeclarationAttributs(String nom, String type, int modifier) throws NoSuchFieldException { + Field field = classe.getDeclaredField(nom); + Assertions.assertEquals(type, field.getType().getName(), + "Type " + nom + " erroné : doit être " + type); + Assertions.assertEquals(modifier, field.getModifiers(), + "Visibilité " + nom + " erronée : doit être " + modifier); + + } + + static Stream constructeursProvider() { + return Stream.of( + arguments("public %s.Event(java.lang.String,org.json.JSONObject)") + ); + } + + @DisplayName("Déclaration des constructeurs") + @ParameterizedTest + @MethodSource("constructeursProvider") + void testConstructeurs1(String signature) { + Assertions.assertTrue(constructeursSignatures.contains(String.format(signature, module)), + String.format("Constructeur non déclaré : doit être %s\nalors que sont déclarés %s", + signature, constructeursSignatures)); + + } + + static Stream methodesProvider() { + return Stream.of( + arguments("getType", "public java.lang.String %s.Event.getType()"), + arguments("getContent", "public org.json.JSONObject %s.Event.getContent()"), + arguments("toString", "public java.lang.String %s.Event.toString()"), + arguments("toJsonObject", "public org.json.JSONObject %s.Event.toJsonObject()"), + arguments("toJson", "public java.lang.String %s.Event.toJson()"), + arguments("fromJson", "public static %s.Event %s.Event.fromJson(java.lang.String) throws org.json.JSONException") + ); + } + + @DisplayName("Déclaration des méthodes") + @ParameterizedTest + @MethodSource("methodesProvider") + void testDeclarationMethodes1(String nom, String signature) { + Assertions.assertTrue(methodesSignatures.contains(String.format(signature, module, module)), + String.format("Méthode non déclarée : doit être %s\nalors que sont déclarés %s", + signature, methodesSignatures)); + } + } + + @DisplayName("02-Instanciation d'un évènement") + @Nested + class InstanciationTest { + + @Test + @DisplayName("Constructeur par défaut") + void TestContructeurParDefaut() { + JSONObject json = new JSONObject("{\"login\":\"riri\"}"); + Event event = new Event("AUTH", json); + Assertions.assertEquals("AUTH", event.getType(), "Type erroné"); + Assertions.assertEquals(json, event.getContent(), "Content erroné"); + } + + + + } + + @DisplayName("03-Instanciation fromJSON") + @Nested + class fromJSONTest { + + static Stream contentProvides() { + return Stream.of( + arguments("AUTH", "{\"type\":\"AUTH\",\"content\":{\"login\":\"riri\"}}"), + arguments("QUIT", "{\"type\": \"QUIT\",\"content\": {}}"), + arguments("MESG", "{\"type\": \"MESG\",\"content\": {\"to\":\"DEST\",\"body\":\"BODY\"}}") + ); + } + @DisplayName("Récupération du type") + @ParameterizedTest + @MethodSource("contentProvides") + void TestContructeurType(String type, String json) { + Event event = Event.fromJson(json); + Assertions.assertEquals(type, event.getType(), "Type erroné"); + } + + static Stream keyProvides() { + return Stream.of( + arguments("login", "riri", "{\"type\":\"AUTH\",\"content\":{\"login\":\"riri\"}}"), + arguments("to", "DEST", "{\"type\": \"MESG\",\"content\": {\"to\":\"DEST\",\"body\":\"BODY\"}}"), + arguments("body", "BODY", "{\"type\": \"MESG\",\"content\": {\"to\":\"DEST\",\"body\":\"BODY\"}}") + ); + } + + @DisplayName("Récupération du contenu") + @ParameterizedTest + @MethodSource("keyProvides") + void TestContructeurContenu(String key, String value, String json) { + Event event = Event.fromJson(json); + Assertions.assertTrue(event.getContent().has(key), "Contenu erroné"); + Assertions.assertEquals(value, event.getContent().get(key), "Contenu erroné"); + } + + @DisplayName("Levée d'exception") + @Test + void TestFromJsonException() { + assertThrows(JSONException.class, () -> Event.fromJson("bonjour")); + } + } + + @Nested + @DisplayName("04-Méthodes") + + class TestMethodes { + @Test + @DisplayName("Méthode toString") + void TestToString() { + Event event = Event.fromJson("{\"type\":\"AUTH\",\"content\":{\"login\":\"riri\"}}"); + String repr = event.toString(); + Assertions.assertEquals("Event{type=AUTH, content={\"login\":\"riri\"}}", repr, "Représentation textuelle erronée"); + } + + @Test + @DisplayName("Méthode toJsonObject") + void TestToJsonObject() { + Event event = Event.fromJson("{\"type\":\"AUTH\",\"content\":{\"login\":\"riri\"}}"); + JSONObject json = event.toJsonObject(); + Assertions.assertTrue(json.has("type"), "Représentation JSON erronée"); + Assertions.assertEquals("AUTH", json.get("type"), "Représentation JSON erronée"); + Assertions.assertTrue(json.has("content"), "Représentation JSON erronée"); + Assertions.assertTrue(json.getJSONObject("content").has("login"), "Représentation JSON erronée"); + Assertions.assertEquals("riri", json.getJSONObject("content").get("login"), "Représentation JSON erronée"); + } + + @Test + @DisplayName("Méthode toJson") + void TestToJson() { + Event event = Event.fromJson("{\"type\":\"AUTH\",\"content\":{\"login\":\"riri\"}}"); + String json = event.toJson(); + Assertions.assertEquals("{\"type\":\"AUTH\",\"content\":{\"login\":\"riri\"}}", + json, "Sérialisation JSON erronée"); + } + + + } + + +} \ No newline at end of file