Merge branch 'dev-event' into 'dev'

feat(event): gestion des événements réseau

See merge request iut_rt/but2/sae302-applicom/bouclyma!5
This commit is contained in:
Emi Boucly 2025-01-08 10:57:59 +01:00
commit ba6d3afe24
9 changed files with 710 additions and 7 deletions

View file

@ -3,6 +3,7 @@ package rtgre.chat;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.embed.swing.SwingFXUtils; import javafx.embed.swing.SwingFXUtils;
@ -22,11 +23,13 @@ import javafx.stage.Stage;
import net.synedra.validatorfx.Check; import net.synedra.validatorfx.Check;
import net.synedra.validatorfx.TooltipWrapper; import net.synedra.validatorfx.TooltipWrapper;
import net.synedra.validatorfx.Validator; import net.synedra.validatorfx.Validator;
import org.json.JSONObject;
import rtgre.chat.graphisme.ContactListViewCell; import rtgre.chat.graphisme.ContactListViewCell;
import rtgre.chat.graphisme.PostListViewCell; import rtgre.chat.graphisme.PostListViewCell;
import rtgre.chat.net.ChatClient; import rtgre.chat.net.ChatClient;
import rtgre.modeles.*; import rtgre.modeles.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -96,6 +99,7 @@ public class ChatController implements Initializable {
avatarMenuItem.setOnAction(this::handleAvatarChange); avatarMenuItem.setOnAction(this::handleAvatarChange);
avatarImageView.setOnMouseClicked(this::handleAvatarChange); avatarImageView.setOnMouseClicked(this::handleAvatarChange);
sendButton.setOnAction(this::onActionSend); sendButton.setOnAction(this::onActionSend);
messageTextField.setOnAction(this::onActionSend);
initContactListView(); initContactListView();
initPostListView(); initPostListView();
@ -108,6 +112,10 @@ public class ChatController implements Initializable {
.decorates(loginTextField) .decorates(loginTextField)
.immediate(); .immediate();
ObservableValue<Boolean> canSendCondition = connectionButton.selectedProperty().not()
.or(contactsListView.getSelectionModel().selectedItemProperty().isNull());
sendButton.disableProperty().bind(canSendCondition);
messageTextField.disableProperty().bind(canSendCondition);
/* /!\ Set-up d'environnement de test /!\ */ /* /!\ Set-up d'environnement de test /!\ */
/* -------------------------------------- */ /* -------------------------------------- */
@ -120,7 +128,9 @@ public class ChatController implements Initializable {
String login = getSelectedContactLogin(); String login = getSelectedContactLogin();
if (login != null) { if (login != null) {
Message message = new Message(login, messageTextField.getText()); 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); contactMap.put(this.contact.getLogin(), this.contact);
LOGGER.info("Nouveau contact : " + contact); LOGGER.info("Nouveau contact : " + contact);
LOGGER.info(contactMap.toString()); LOGGER.info(contactMap.toString());
Matcher matcher = hostPortPattern.matcher("localhost:2024"); Matcher matcher = hostPortPattern.matcher(hostComboBox.getValue());
matcher.matches(); matcher.matches();
String host = matcher.group(1); String host = matcher.group(1);
int port = (matcher.group(2) != null) ? Integer.parseInt(matcher.group(2)) : 2024; int port = (matcher.group(2) != null) ? Integer.parseInt(matcher.group(2)) : 2024;
try { try {
LOGGER.info(host + ":" + port);
this.client = new ChatClient(host, port, this); this.client = new ChatClient(host, port, this);
initContactListView(); initContactListView();
initPostListView(); initPostListView();
clearLists(); clearLists();
contactMap.add(this.contact); contactMap.add(this.contact);
this.contact.setConnected(true); this.contact.setConnected(true);
client.sendAuthEvent(contact);
client.sendEvent(new rtgre.modeles.Event(rtgre.modeles.Event.LIST_CONTACTS, new JSONObject()));
initContactListView(); initContactListView();
initPostListView(); initPostListView();
this.statusLabel.setText("Connected to %s@%s:%s".formatted(this.contact.getLogin(), host, port)); 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); connectionButton.setSelected(false);
} }
} else if (!connectionButton.isSelected()) { } else if (!connectionButton.isSelected()) {
this.client.sendQuitEvent();
clearLists(); clearLists();
this.client.close(); if (this.client.isConnected()) {
this.contact.setConnected(false); this.contact.setConnected(false);
}
statusLabel.setText("not connected to " + hostComboBox.getValue()); statusLabel.setText("not connected to " + hostComboBox.getValue());
} }
@ -260,8 +277,55 @@ public class ChatController implements Initializable {
if (contactSelected != null) { if (contactSelected != null) {
LOGGER.info("Clic sur " + contactSelected); 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); postsObservableList.add(postSys);
client.sendListPostEvent(0, contactSelected.getLogin());
postListView.refresh(); 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());
}
}
} }

View file

@ -1,8 +1,15 @@
package rtgre.chat.net; package rtgre.chat.net;
import javafx.application.Platform;
import org.json.JSONObject;
import rtgre.chat.ChatController; 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.IOException;
import java.io.InputStreamReader;
import java.util.logging.Logger; import java.util.logging.Logger;
import static rtgre.chat.ChatApplication.LOGGER; import static rtgre.chat.ChatApplication.LOGGER;
@ -26,6 +33,47 @@ public class ChatClient extends ClientTCP {
this.listener = listener; 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 @Override
public void receiveLoop() { public void receiveLoop() {
LOGGER.info(RED + "Boucle de réception de messages..." + RST); LOGGER.info(RED + "Boucle de réception de messages..." + RST);
@ -34,6 +82,9 @@ public class ChatClient extends ClientTCP {
String message = this.receive(); String message = this.receive();
LOGGER.info(RED + "Réception: " + message + RST); LOGGER.info(RED + "Réception: " + message + RST);
LOGGER.info(RED + message + RST); LOGGER.info(RED + message + RST);
if (listener != null) {
Platform.runLater(() -> listener.handleEvent(Event.fromJson(message)));
}
} }
} catch (IOException e) { } catch (IOException e) {
LOGGER.severe("[%s] %s".formatted(ipPort, e)); LOGGER.severe("[%s] %s".formatted(ipPort, e));
@ -50,4 +101,8 @@ public class ChatClient extends ClientTCP {
public ChatController getListener() { public ChatController getListener() {
return listener; return listener;
} }
public void sendMessageEvent(Message msg) {
sendEvent(new Event("MESG", msg.toJsonObject()));
}
} }

View file

@ -1,6 +1,8 @@
package rtgre.modeles; package rtgre.modeles;
import org.json.JSONObject;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
@ -101,6 +103,20 @@ public class Contact {
return 0; 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 { 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. * Renvoie une sous-image en fonction d'une banque d'image et d'un login.

View file

@ -9,4 +9,16 @@ public class ContactMap extends TreeMap<String, Contact> {
public Contact getContact(String login) { public Contact getContact(String login) {
return this.get(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));
}
} }

View file

@ -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);
}
}

View file

@ -1,5 +1,10 @@
package rtgre.server; package rtgre.server;
import org.json.JSONException;
import org.json.JSONObject;
import rtgre.chat.net.ChatClient;
import rtgre.modeles.*;
import java.io.*; import java.io.*;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
@ -17,6 +22,8 @@ public class ChatServer {
private static final Logger LOGGER = Logger.getLogger(ChatServer.class.getCanonicalName()); private static final Logger LOGGER = Logger.getLogger(ChatServer.class.getCanonicalName());
private Vector<ChatClientHandler> clientList; private Vector<ChatClientHandler> clientList;
private PostVector postVector;
private ContactMap contactMap;
static { static {
try { try {
@ -31,6 +38,7 @@ public class ChatServer {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
ChatServer server = new ChatServer(2024); ChatServer server = new ChatServer(2024);
daisyConnect();
server.acceptClients(); server.acceptClients();
} }
@ -43,6 +51,9 @@ public class ChatServer {
passiveSock = new ServerSocket(port); passiveSock = new ServerSocket(port);
LOGGER.info("Serveur en écoute " + passiveSock); LOGGER.info("Serveur en écoute " + passiveSock);
clientList = new Vector<>(); clientList = new Vector<>();
contactMap = new ContactMap();
postVector = new PostVector();
contactMap.loadDefaultContacts();
} }
public void close() throws IOException { public void close() throws IOException {
@ -93,7 +104,7 @@ public class ChatServer {
*/ */
private void handleNewClient(Socket sock) throws IOException { private void handleNewClient(Socket sock) throws IOException {
ChatClientHandler client = new ChatClientHandler(sock); ChatClientHandler client = new ChatClientHandler(sock);
Thread clientLoop = new Thread(client::echoLoop); Thread clientLoop = new Thread(client::eventReceiveLoop);
clientLoop.start(); clientLoop.start();
clientList.add(client); clientList.add(client);
LOGGER.fine("Ajout du client [%s] dans la liste (%d clients connectés)" LOGGER.fine("Ajout du client [%s] dans la liste (%d clients connectés)"
@ -101,6 +112,45 @@ public class ChatServer {
//client.echoLoop(); //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 { private class ChatClientHandler {
public static final String END_MESSAGE = "fin"; public static final String END_MESSAGE = "fin";
/** /**
@ -120,6 +170,8 @@ public class ChatServer {
*/ */
private String ipPort; private String ipPort;
private Contact user;
/** /**
* Initialise les attributs {@link #sock} (socket connecté au client), * Initialise les attributs {@link #sock} (socket connecté au client),
* {@link #out} (flux de caractères UTF-8 en sortie) et * {@link #out} (flux de caractères UTF-8 en sortie) et
@ -160,6 +212,123 @@ public class ChatServer {
close(); 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 { public void send(String message) throws IOException {
LOGGER.finest("send: %s".formatted(message)); LOGGER.finest("send: %s".formatted(message));
out.println(message); out.println(message);
@ -189,7 +358,7 @@ public class ChatServer {
public String receive() throws IOException { public String receive() throws IOException {
String message = in.readLine(); String message = in.readLine();
LOGGER.finest("receive: %s".formatted(message)); LOGGER.info("receive: %s".formatted(message));
if (message == null) { if (message == null) {
throw new IOException("End of the stream has been reached"); throw new IOException("End of the stream has been reached");
} }
@ -201,6 +370,8 @@ public class ChatServer {
try { try {
sock.close(); sock.close();
removeClient(this); removeClient(this);
user.setConnected(false);
sendEventToAllContacts(new Event(Event.CONT, user.toJsonObject()));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -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<String> constructeursSignatures;
static List<String> 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<Arguments> 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 <projet>/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é");
}
}
}

View file

@ -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<String> constructeursSignatures;
static List<String> 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<Arguments> 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<Arguments> 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<Arguments> 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<Arguments> 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<Arguments> 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<Arguments> 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");
}
}
}