S SmartDocs
Series: Angelia java 78 lines · Updated 2026-09-27

ProtocolTest.java

Angelia/Java/Week_6_7/code/08_chat_protocol/ProtocolTest.java

/**
 * 協定解析器的測試(用 assert;也可以改寫成 JUnit)。
 *
 * 執行:javac *.java && java -ea ProtocolTest
 */
public class ProtocolTest {

    /** 「往返」性質:任何合法訊息 encode 之後再 parse,必須得到相等的物件。 */
    static void roundTrip(ChatMessage m) {
        String line = ChatMessage.encode(m);
        ChatMessage back = ChatMessage.parse(line);
        assert back.equals(m) : "往返失敗:" + m + " → \"" + line + "\" → " + back;
        System.out.println("  " + String.format("%-40s", line) + " ⇄ " + back);
    }

    static void expectError(String line) {
        try {
            ChatMessage m = ChatMessage.parse(line);
            assert false : "應該要失敗卻解析成功:" + line + " → " + m;
        } catch (ChatMessage.ProtocolException e) {
            System.out.println("  \"" + line + "\" → 拒絕:" + e.getMessage());
        }
    }

    public static void main(String[] args) throws java.io.IOException {
        System.out.println("=== 往返測試(record 自動有 equals,才能這樣比較)===");
        roundTrip(new ChatMessage.Login("angelia"));
        roundTrip(new ChatMessage.Join("general"));
        roundTrip(new ChatMessage.Say("general", "hello world, with spaces!"));
        roundTrip(new ChatMessage.Quit());
        roundTrip(new ChatMessage.Ok(""));
        roundTrip(new ChatMessage.Ok("welcome angelia"));
        roundTrip(new ChatMessage.Err(401, "login first"));
        roundTrip(new ChatMessage.Msg("general", "bob", "多位元組字元也沒問題:café 日本語"));
        roundTrip(new ChatMessage.Bye());

        System.out.println("=== 不合法的輸入必須被拒絕,而不是默默接受 ===");
        expectError("");
        expectError("DANCE");
        expectError("LOGIN");
        expectError("LOGIN two words");
        expectError("SAY general");
        expectError("ERR notanumber oops");
        expectError("MSG general bob");

        System.out.println("=== 指令不分大小寫 ===");
        assert ChatMessage.parse("login angelia").equals(new ChatMessage.Login("angelia"));

        System.out.println("=== 用 FakeChatClient 測 UI 邏輯(不需要網路)===");
        StringBuilder screen = new StringBuilder();
        ChatClient client = new FakeChatClient();
        client.addListener(new ChatClient.Listener() {         // 匿名類別實作監聽者介面
            public void onMessage(String room, String from, String text) {
                screen.append("[").append(room).append("] ").append(from).append(": ").append(text).append("\n");
            }
            public void onError(int code, String text) {
                screen.append("ERROR ").append(code).append(": ").append(text).append("\n");
            }
            public void onDisconnected() {
                screen.append("(disconnected)\n");
            }
        });
        client.connect("localhost", 5555, "angelia");
        client.say("general", "hi");                          // 還沒 join → 錯誤
        client.join("general");
        client.say("general", "hi again");
        client.disconnect();
        System.out.print(screen);
        assert screen.toString().equals(
                "ERROR 403: not in room general\n[general] angelia: hi again\n(disconnected)\n");
        try {
            client.say("general", "after disconnect");
        } catch (IllegalStateException e) {
            System.out.println("  斷線後操作 → IllegalStateException:" + e.getMessage());
        }
        System.out.println("全部通過。");
    }
}

Related articles