/**
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.waveprotocol.box.consoleclient;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.waveprotocol.box.server.util.testing.Matchers.contains;
import static org.waveprotocol.box.server.util.testing.Matchers.doesNotContain;
import junit.framework.TestCase;
import org.waveprotocol.box.common.IndexEntry;
import org.waveprotocol.box.common.IndexWave;
import org.waveprotocol.box.consoleclient.ScrollableWaveView.RenderMode;
import org.waveprotocol.box.server.util.testing.TestingConstants;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.wave.ParticipantId;
import java.io.IOException;
import java.security.Permission;
import java.util.List;
import java.util.Set;
/**
* Functional tests for the {@link ConsoleClient}.
*
* @author mk.mateng@gmail.com (Michael Kuntzman)
*/
public class ConsoleClientTest extends TestCase implements TestingConstants {
/** An exception to identify System.exit() calls */
private static class ExitException extends SecurityException {
public final int status;
public ExitException(int status) {
super("Exit with status " + status);
this.status = status;
}
}
/** The client under test */
private ConsoleClient client;
/** The backend of the client under test. Null if the client is not connected. */
private ClientBackend backend = null;
/** The testing utility for the client under test. Null if the client is not connected. */
private ClientTestingUtil util = null;
@Override
public void setUp() throws IOException {
client = new ConsoleClient(ClientTestingUtil.backendSpyFactory,
ClientTestingUtil.getMockConsoleRenderer());
}
@Override
public void tearDown() {
client.disconnect();
}
// Tests
/**
* The client should be offline when created before anything is called. A quick test to avoid
* untested assumptions in the other tests.
*/
public void testStartsOffline() {
assertFalse(client.isConnected());
}
/** "connect" command should connect the client to the correct user, server, and port */
public void testConnectConnectsWithCorrectUser() {
connect();
assertTrue(client.isConnected());
assertEquals(USER, backend.getUserId().getAddress());
}
/** "connect" command should open the index wave */
public void testConnectOpensIndexWave() {
connect();
// Only the index wave should be open.
assertEquals(1, util.getOpenWavesCount(true));
assertNotNull(backend.getIndexWave());
assertTrue(client.isInboxOpen());
// No conversation wave should be displayed yet.
assertFalse(client.isWaveOpen());
}
/** "new" command should create a single wave and open it, but not dislay it in the UI */
public void testNewCreatesOneWaveAndOpensItButDoesNotDisplayIt() {
connect();
createNewWave();
Set<WaveId> openWaves = util.getOpenWaveIds(true);
ClientWaveView indexWave = backend.getIndexWave();
// There should be 2 waves: the index wave and our new wave.
assertEquals(2, openWaves.size());
assertNotNull(indexWave);
assertTrue(client.isInboxOpen());
// The new wave should not be displayed yet (need to call "open" command to display it).
assertFalse(client.isWaveOpen());
// The index should contain only the new wave.
List<IndexEntry> index = IndexWave.getIndexEntries(indexWave.getWavelets());
assertEquals(1, index.size());
WaveId newWaveId = index.get(0).getWaveId();
assertThat(openWaves, contains(newWaveId));
}
/** "new" command should only add the user, and nothing else (no other users and no content) */
public void testNewOnlyAddsSelf() {
connect();
createNewWave();
ClientWaveView newWave = util.getFirstWave();
String newWaveContent = util.getText(newWave);
Set<ParticipantId> participants = util.getAllParticipants(newWave);
assertEquals("", newWaveContent);
assertEquals(1, participants.size());
assertThat(participants, contains(backend.getUserId()));
}
/** "open" command should open a wave but not create new ones */
public void testOpenHasNoSideEffects() {
connect();
createNewWave();
// Make sure the wave is not open in the UI yet.
assertFalse(client.isWaveOpen());
int waveCount = util.getOpenWavesCount(true);
// Open the wave in the UI (it should already be open in the backend).
openWave(0);
// The wave should now be open in UI.
assertTrue(client.isWaveOpen());
// No waves were created/deleted.
assertEquals(waveCount, util.getOpenWavesCount(true));
}
/** "open" command should open correct wave */
public void testOpenOpensCorrectWave() {
connect();
// We need at least 5 entries to cover all corner cases (1st, 2nd, last, before-last, middle).
// We use 6 to be safe.
for (int i=0; i<6; ++i) {
createNewWave();
}
List<IndexEntry> index = IndexWave.getIndexEntries(backend.getIndexWave().getWavelets());
for (int i=0; i < index.size(); ++i) {
openWave(i);
WaveId openedWaveId = client.getOpenWaveId();
WaveId waveIdFromIndex = index.get(i).getWaveId();
assertEquals(waveIdFromIndex, openedWaveId);
}
}
/** "open" command should open the wave in normal display mode */
public void testOpenOpensWaveInNormalDisplayMode() {
connect();
createNewWave();
openWave(0);
assertEquals(RenderMode.NORMAL, client.getRenderingMode());
// Force xml mode.
setViewMode("xml");
// Check that the render mode really changed, so that we don't get a false
// positive result below.
assertEquals(RenderMode.XML, client.getRenderingMode());
// Re-open the wave.
openWave(0);
// Should be in normal mode again.
assertEquals(RenderMode.NORMAL, client.getRenderingMode());
}
/** "add" command should not add/delete/modify waves */
public void testAddHasNoSideEffects() {
connect();
createNewWave();
openWave(0);
int oldWaveCount = util.getOpenWavesCount(true);
WaveId oldOpenWaveId = client.getOpenWaveId();
String oldContent = util.getText(oldOpenWaveId);
addUser(OTHER_PARTICIPANT);
int newWaveCount = util.getOpenWavesCount(true);
WaveId newOpenWaveId = client.getOpenWaveId();
String newContent = util.getText(newOpenWaveId);
// No waves were created/deleted.
assertEquals(oldWaveCount, newWaveCount);
// The same wave should be open as before.
assertEquals(oldOpenWaveId, newOpenWaveId);
// The wave content was not changed.
assertEquals(oldContent, newContent);
}
/** "add" command should add the specified user */
public void testAddAddsUser() {
connect();
createNewWave();
openWave(0);
Set<ParticipantId> oldUsers = util.getAllParticipants(client.getOpenWaveId());
addUser(OTHER_PARTICIPANT);
Set<ParticipantId> newUsers = util.getAllParticipants(client.getOpenWaveId());
assertThat(newUsers, contains(OTHER_PARTICIPANT));
newUsers.remove(OTHER_PARTICIPANT);
assertEquals(oldUsers, newUsers);
}
/** "remove" command should not add/delete/modify waves */
public void testRemoveHasNoSideEffects() {
connect();
createNewWave();
openWave(0);
addUser(OTHER_PARTICIPANT);
int oldWaveCount = util.getOpenWavesCount(true);
WaveId oldOpenWaveId = client.getOpenWaveId();
String oldContent = util.getText(oldOpenWaveId);
removeUser(OTHER_PARTICIPANT);
int newWaveCount = util.getOpenWavesCount(true);
WaveId newOpenWaveId = client.getOpenWaveId();
String newContent = util.getText(newOpenWaveId);
// No waves were created/deleted.
assertEquals(oldWaveCount, newWaveCount);
// The same wave should be open as before.
assertEquals(oldOpenWaveId, newOpenWaveId);
// The wave content was not changed.
assertEquals(oldContent, newContent);
}
/** "remove" command should remove the specified user */
public void testRemoveRemovesUser() {
connect();
createNewWave();
openWave(0);
addUser(OTHER_PARTICIPANT);
Set<ParticipantId> oldUsers = util.getAllParticipants(client.getOpenWaveId());
removeUser(OTHER_PARTICIPANT);
Set<ParticipantId> newUsers = util.getAllParticipants(client.getOpenWaveId());
assertThat(newUsers, doesNotContain(OTHER_PARTICIPANT));
newUsers.add(OTHER_PARTICIPANT);
assertEquals(oldUsers, newUsers);
}
/** "read" command should mark all known waves as read */
public void testReadMarksAllWavesAsRead() {
connect();
for (int i=0; i<3; ++i) {
createNewWave();
}
List<IndexEntry> index = IndexWave.getIndexEntries(backend.getIndexWave().getWavelets());
// Check that all waves are unread.
for (int i=0; i < index.size(); ++i) {
assertFalse(isRead(i));
}
markAllRead();
// All waves should now be read.
for (int i=0; i < index.size(); ++i) {
assertTrue(isRead(i));
}
}
/** "scroll" command should set the correct scrolling step */
public void testScrollSetsCorrectScrollingStep() {
int oldStep = client.getScrollLines();
int newStep = oldStep + 3;
setScrollStep(newStep);
assertEquals(newStep, client.getScrollLines());
}
/** "view" command should set the correct render mode */
public void testViewSetsCorrectRenderMode() {
connect();
createNewWave();
openWave(0);
assertEquals(RenderMode.NORMAL, client.getRenderingMode());
setViewMode("xml");
assertEquals(RenderMode.XML, client.getRenderingMode());
setViewMode("normal");
assertEquals(RenderMode.NORMAL, client.getRenderingMode());
}
/** "quit" command should clean up and shut down the client */
public void testQuitExitsCleanly() {
connect();
createNewWave();
openWave(0);
// Set up a custom security manager to intercept System.exit() calls.
// Taken from http://stackoverflow.com/questions/309396/java-how-to-test-methods-that-call-system-exit
SecurityManager oldSecurityManager = System.getSecurityManager();
System.setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
// Allow anything.
}
@Override
public void checkPermission(Permission perm, Object context) {
// Allow anything.
}
@Override
public void checkExit(int status) {
throw new ExitException(status);
}
});
int exitStatus = -1;
try {
quit();
} catch (ExitException e) {
exitStatus = e.status;
} finally {
System.setSecurityManager(oldSecurityManager);
// The client should exit without an error.
assertEquals(0, exitStatus);
// The client should have disconnected.
assertFalse(client.isConnected());
}
}
/** A simple text message should be added to the wave as is, with no other side effects */
public void testTextMessageIsAddedWithNoSideEffects() {
connect();
createNewWave();
openWave(0);
int oldWaveCount = util.getOpenWavesCount(true);
WaveId oldOpenWaveId = client.getOpenWaveId();
String oldContent = util.getText(oldOpenWaveId);
Set<ParticipantId> oldUsers = util.getAllParticipants(oldOpenWaveId);
assertEquals("", oldContent);
postText(MESSAGE);
int newWaveCount = util.getOpenWavesCount(true);
WaveId newOpenWaveId = client.getOpenWaveId();
String newContent = util.getText(newOpenWaveId);
Set<ParticipantId> newUsers = util.getAllParticipants(newOpenWaveId);
// No waves were created/deleted.
assertEquals(oldWaveCount, newWaveCount);
// The same wave should be open as before.
assertEquals(oldOpenWaveId, newOpenWaveId);
// No users were added/deleted.
assertEquals(oldUsers, newUsers);
// The wave text should contain the message and nothing else.
assertEquals(MESSAGE, newContent);
}
// Utiltity methods
/** Add the specified user to the current wave (run the "/add" command) */
private void addUser(ParticipantId user) {
assertTrue(client.isConnected());
assertTrue(client.isWaveOpen());
client.processLine("/add " + user.getAddress());
backend.waitForAccumulatedEventsToProcess();
}
/** Connect the client (run the "/connect" command) */
private void connect() {
assertFalse(client.isConnected());
client.processLine(String.format("/connect %s %s:%d %s", USER, DOMAIN, PORT, PASSWORD));
client.getBackend().waitForAccumulatedEventsToProcess();
// We can now use the client backend and testing utility for the rest of the test:
backend = client.getBackend();
util = new ClientTestingUtil(backend);
}
/** Create a new wave (run the "/new" command) */
private void createNewWave() {
assertTrue(client.isConnected());
client.processLine("/new");
backend.waitForAccumulatedEventsToProcess();
}
/**
* Checks that the latest change to the specified inbox entry have been read.
*
* @param entry the number (zero-based) of the inbox entry to check
* @return true if the two versions are the same, or false otherwise
*/
private boolean isRead(int entry) {
List<IndexEntry> index = IndexWave.getIndexEntries(backend.getIndexWave().getWavelets());
ClientWaveView wave = backend.getWave(index.get(entry).getWaveId());
return client.isRead(wave);
}
/** Mark all waves as read (run the "/read" command) */
private void markAllRead() {
assertTrue(client.isConnected());
client.processLine("/read");
backend.waitForAccumulatedEventsToProcess();
}
/**
* Open the specified wave (run the "/open" command).
*
* @param entry number of the wave in the index (zero-based).
*/
private void openWave(int entry) {
assertTrue(client.isConnected());
client.processLine("/open " + entry);
backend.waitForAccumulatedEventsToProcess();
}
/** Enter a text message in the current wave */
private void postText(String message) {
// Check that it's not a command and the client is connected and has an open wave.
assertFalse(message.startsWith("/"));
assertTrue(client.isConnected());
assertTrue(client.isWaveOpen());
client.processLine(message);
backend.waitForAccumulatedEventsToProcess();
}
/** Remove the specified user from the current wave (run the "/remove" command) */
private void removeUser(ParticipantId user) {
assertTrue(client.isConnected());
assertTrue(client.isWaveOpen());
client.processLine("/remove " + user.getAddress());
backend.waitForAccumulatedEventsToProcess();
}
/** Sets the scrolling step (runs the "/scroll" command) */
private void setScrollStep(int lines) {
client.processLine("/scroll " + lines);
}
/** Sets the view to the specified mode (runs the "/view" command) */
private void setViewMode(String viewMode) {
assertTrue(client.isConnected());
assertTrue(client.isWaveOpen());
client.processLine("/view " + viewMode);
}
/** Quit the client (run the "/quit" command) */
private void quit() {
client.processLine("/quit");
}
}