mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-12-06 13:11:08 +01:00
Implement support for roster versioning
Roster versioning is defined in RFC 6121, section 2.2.6; the protocol was originally described in XEP-0237. Fixes SMACK-399
This commit is contained in:
parent
e7a2cad401
commit
c06b0a7720
14 changed files with 1300 additions and 147 deletions
222
test-unit/org/jivesoftware/smack/DefaultRosterStoreTest.java
Normal file
222
test-unit/org/jivesoftware/smack/DefaultRosterStoreTest.java
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/**
|
||||
* All rights reserved. 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.jivesoftware.smack;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.packet.RosterPacket;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.Item;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.ItemStatus;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.ItemType;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
/**
|
||||
* Tests the implementation of {@link DefaultRosterStore}.
|
||||
*
|
||||
* @author Lars Noschinski
|
||||
*/
|
||||
public class DefaultRosterStoreTest {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tmpFolder = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that opening an uninitialized directory fails.
|
||||
*/
|
||||
@Test
|
||||
public void testStoreUninitialized() throws IOException {
|
||||
File storeDir = tmpFolder.newFolder();
|
||||
assertNull(DefaultRosterStore.open(storeDir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an initialized directory is empty.
|
||||
*/
|
||||
@Test
|
||||
public void testStoreInitializedEmpty() throws IOException {
|
||||
File storeDir = tmpFolder.newFolder();
|
||||
DefaultRosterStore store = DefaultRosterStore.init(storeDir);
|
||||
assertNotNull("Initialization returns store", store);
|
||||
assertEquals("Freshly initialized store must have empty version",
|
||||
"", store.getRosterVersion());
|
||||
assertEquals("Freshly initialized store must have no entries",
|
||||
0, store.getEntries().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding and removing entries
|
||||
*/
|
||||
@Test
|
||||
public void testStoreAddRemove() throws IOException {
|
||||
File storeDir = tmpFolder.newFolder();
|
||||
DefaultRosterStore store = DefaultRosterStore.init(storeDir);
|
||||
|
||||
assertEquals("Initial roster version", "", store.getRosterVersion());
|
||||
|
||||
String userName = "user@example.com";
|
||||
|
||||
final RosterPacket.Item item1 = new Item(userName, null);
|
||||
final String version1 = "1";
|
||||
store.addEntry(item1, version1);
|
||||
|
||||
assertEquals("Adding entry sets version correctly", version1, store.getRosterVersion());
|
||||
|
||||
RosterPacket.Item storedItem = store.getEntry(userName);
|
||||
assertNotNull("Added entry not found found", storedItem);
|
||||
assertEquals("User of added entry",
|
||||
item1.getUser(), storedItem.getUser());
|
||||
assertEquals("Name of added entry",
|
||||
item1.getName(), storedItem.getName());
|
||||
assertEquals("Groups", item1.getGroupNames(), storedItem.getGroupNames());
|
||||
assertEquals("ItemType of added entry",
|
||||
item1.getItemType(), storedItem.getItemType());
|
||||
assertEquals("ItemStatus of added entry",
|
||||
item1.getItemStatus(), storedItem.getItemStatus());
|
||||
|
||||
|
||||
final String version2 = "2";
|
||||
final RosterPacket.Item item2 = new Item(userName, "Ursula Example");
|
||||
item2.addGroupName("users");
|
||||
item2.addGroupName("examples");
|
||||
item2.setItemStatus(ItemStatus.subscribe);
|
||||
item2.setItemType(ItemType.none);
|
||||
store.addEntry(item2,version2);
|
||||
assertEquals("Updating entry sets version correctly", version2, store.getRosterVersion());
|
||||
storedItem = store.getEntry(userName);
|
||||
assertNotNull("Added entry not found", storedItem);
|
||||
assertEquals("User of added entry",
|
||||
item2.getUser(), storedItem.getUser());
|
||||
assertEquals("Name of added entry",
|
||||
item2.getName(), storedItem.getName());
|
||||
assertEquals("Groups", item2.getGroupNames(), storedItem.getGroupNames());
|
||||
assertEquals("ItemType of added entry",
|
||||
item2.getItemType(), storedItem.getItemType());
|
||||
assertEquals("ItemStatus of added entry",
|
||||
item2.getItemStatus(), storedItem.getItemStatus());
|
||||
|
||||
List<Item> entries = store.getEntries();
|
||||
assertEquals("Number of entries", 1, entries.size());
|
||||
|
||||
final RosterPacket.Item item3 = new Item("foobar@example.com", "Foo Bar");
|
||||
item3.addGroupName("The Foo Fighters");
|
||||
item3.addGroupName("Bar Friends");
|
||||
item3.setItemStatus(ItemStatus.unsubscribe);
|
||||
item3.setItemType(ItemType.both);
|
||||
|
||||
final RosterPacket.Item item4 = new Item("baz@example.com", "Baba Baz");
|
||||
item4.addGroupName("The Foo Fighters");
|
||||
item4.addGroupName("Bar Friends");
|
||||
item4.setItemStatus(ItemStatus.subscribe);
|
||||
item4.setItemType(ItemType.both);
|
||||
|
||||
ArrayList<Item> items34 = new ArrayList<RosterPacket.Item>();
|
||||
items34.add(item3);
|
||||
items34.add(item4);
|
||||
|
||||
String version3 = "3";
|
||||
store.resetEntries(items34, version3);
|
||||
|
||||
storedItem = store.getEntry("foobar@example.com");
|
||||
assertNotNull("Added entry not found", storedItem);
|
||||
assertEquals("User of added entry",
|
||||
item3.getUser(), storedItem.getUser());
|
||||
assertEquals("Name of added entry",
|
||||
item3.getName(), storedItem.getName());
|
||||
assertEquals("Groups", item3.getGroupNames(), storedItem.getGroupNames());
|
||||
assertEquals("ItemType of added entry",
|
||||
item3.getItemType(), storedItem.getItemType());
|
||||
assertEquals("ItemStatus of added entry",
|
||||
item3.getItemStatus(), storedItem.getItemStatus());
|
||||
|
||||
|
||||
storedItem = store.getEntry("baz@example.com");
|
||||
assertNotNull("Added entry not found", storedItem);
|
||||
assertEquals("User of added entry",
|
||||
item4.getUser(), storedItem.getUser());
|
||||
assertEquals("Name of added entry",
|
||||
item4.getName(), storedItem.getName());
|
||||
assertEquals("Groups", item4.getGroupNames(), storedItem.getGroupNames());
|
||||
assertEquals("ItemType of added entry",
|
||||
item4.getItemType(), storedItem.getItemType());
|
||||
assertEquals("ItemStatus of added entry",
|
||||
item4.getItemStatus(), storedItem.getItemStatus());
|
||||
|
||||
entries = store.getEntries();
|
||||
assertEquals("Number of entries", 2, entries.size());
|
||||
|
||||
String version4 = "4";
|
||||
store.removeEntry("baz@example.com", version4);
|
||||
assertEquals("Removing entry sets version correctly",
|
||||
version4, store.getRosterVersion());
|
||||
assertNull("Removed entry is gone", store.getEntry(userName));
|
||||
|
||||
entries = store.getEntries();
|
||||
assertEquals("Number of entries", 1, entries.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding entries with evil characters
|
||||
*/
|
||||
@Test
|
||||
public void testAddEvilChars() throws IOException {
|
||||
File storeDir = tmpFolder.newFolder();
|
||||
DefaultRosterStore store = DefaultRosterStore.init(storeDir);
|
||||
|
||||
String user = "../_#;\"'\\&@example.com";
|
||||
String name = "\n../_#\0\t;\"'&@\\";
|
||||
String group1 = "\t;\"'&@\\\n../_#\0";
|
||||
String group2 = "#\0\t;\"'&@\\\n../_";
|
||||
|
||||
Item item = new Item(user, name);
|
||||
item.setItemStatus(ItemStatus.unsubscribe);
|
||||
item.setItemType(ItemType.to);
|
||||
item.addGroupName(group1);
|
||||
item.addGroupName(group2);
|
||||
store.addEntry(item, "a-version");
|
||||
Item storedItem = store.getEntry(user);
|
||||
|
||||
assertNotNull("Added entry not found", storedItem);
|
||||
assertEquals("User of added entry",
|
||||
item.getUser(), storedItem.getUser());
|
||||
assertEquals("Name of added entry",
|
||||
item.getName(), storedItem.getName());
|
||||
assertEquals("Groups", item.getGroupNames(), storedItem.getGroupNames());
|
||||
assertEquals("ItemType of added entry",
|
||||
item.getItemType(), storedItem.getItemType());
|
||||
assertEquals("ItemStatus of added entry",
|
||||
item.getItemStatus(), storedItem.getItemStatus());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -355,6 +355,25 @@ public class RosterTest {
|
|||
assertSame("Wrong number of roster entries.", 4, roster.getEntries().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that roster pushes with invalid from are ignored.
|
||||
*
|
||||
* @see <a href="http://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push">RFC 6121, Section 2.1.6</a>
|
||||
*/
|
||||
@Test(timeout=5000)
|
||||
public void testIgnoreInvalidFrom() {
|
||||
RosterPacket packet = new RosterPacket();
|
||||
packet.setType(Type.SET);
|
||||
packet.setTo(connection.getUser());
|
||||
packet.setFrom("mallory@example.com");
|
||||
packet.addRosterItem(new Item("spam@example.com", "Cool products!"));
|
||||
|
||||
// Simulate receiving the roster push
|
||||
connection.processPacket(packet);
|
||||
|
||||
assertNull("Contact was added to roster", connection.getRoster().getEntry("spam@example.com"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if adding an user with an empty group is equivalent with providing
|
||||
* no group.
|
||||
|
|
|
|||
242
test-unit/org/jivesoftware/smack/RosterVersioningTest.java
Normal file
242
test-unit/org/jivesoftware/smack/RosterVersioningTest.java
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
/**
|
||||
* All rights reserved. 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.jivesoftware.smack;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.IQ.Type;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.RosterPacket;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.Item;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.ItemType;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
/**
|
||||
* Tests that verify the correct behavior of the {@see Roster} implementation
|
||||
* with regard to roster versioning
|
||||
*
|
||||
* @see Roster
|
||||
* @see <a href="http://xmpp.org/rfcs/rfc6121.html#roster">Managing the Roster</a>
|
||||
* @author Fabian Schuetz
|
||||
* @author Lars Noschinski
|
||||
*/
|
||||
public class RosterVersioningTest {
|
||||
|
||||
private DummyConnection connection;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tmpFolder = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// Uncomment this to enable debug output
|
||||
//Connection.DEBUG_ENABLED = true;
|
||||
|
||||
DefaultRosterStore store = DefaultRosterStore.init(tmpFolder.newFolder("store"));
|
||||
populateStore(store);
|
||||
|
||||
ConnectionConfiguration conf = new ConnectionConfiguration("dummy");
|
||||
conf.setRosterStore(store);
|
||||
connection = new DummyConnection(conf);
|
||||
connection.connect();
|
||||
|
||||
connection.setRosterVersioningSupported();
|
||||
|
||||
connection.login("rostertest", "secret");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that receiving an empty roster result causes the roster to be populated
|
||||
* by all entries of the roster store.
|
||||
*/
|
||||
@Test(timeout = 5000)
|
||||
public void testEqualVersionStored() throws InterruptedException, IOException {
|
||||
connection.getRoster().reload();
|
||||
answerWithEmptyRosterResult();
|
||||
|
||||
Roster roster = connection.getRoster();
|
||||
Collection<RosterEntry> entries = roster.getEntries();
|
||||
assertSame("Size of the roster", 3, entries.size());
|
||||
|
||||
HashSet<Item> items = new HashSet<Item>();
|
||||
for (RosterEntry entry : entries) {
|
||||
items.add(RosterEntry.toRosterItem(entry));
|
||||
}
|
||||
RosterStore store = DefaultRosterStore.init(tmpFolder.newFolder());
|
||||
populateStore(store);
|
||||
assertEquals("Elements of the roster", new HashSet<Item>(store.getEntries()), items);
|
||||
|
||||
for (RosterEntry entry : entries) {
|
||||
assertTrue("joe stevens".equals(entry.getName()) || "geoff hurley".equals(entry.getName())
|
||||
|| "higgins mcmann".equals(entry.getName()));
|
||||
}
|
||||
Collection<RosterGroup> groups = roster.getGroups();
|
||||
assertSame(3, groups.size());
|
||||
|
||||
for (RosterGroup group : groups) {
|
||||
assertTrue("all".equals(group.getName()) || "friends".equals(group.getName())
|
||||
|| "partners".equals(group.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a non-empty roster result empties the store.
|
||||
*/
|
||||
@Test(timeout = 5000)
|
||||
public void testOtherVersionStored() throws InterruptedException {
|
||||
connection.getRoster().reload();
|
||||
|
||||
Item vaglafItem = vaglafItem();
|
||||
|
||||
// We expect that the roster request is the only packet sent. This is not part of the specification,
|
||||
// but a shortcut in the test implementation.
|
||||
Packet sentPacket = connection.getSentPacket();
|
||||
if (sentPacket instanceof RosterPacket) {
|
||||
RosterPacket sentRP = (RosterPacket)sentPacket;
|
||||
RosterPacket answer = new RosterPacket();
|
||||
answer.setPacketID(sentRP.getPacketID());
|
||||
answer.setType(Type.RESULT);
|
||||
answer.setTo(sentRP.getFrom());
|
||||
|
||||
answer.setVersion("newVersion");
|
||||
answer.addRosterItem(vaglafItem);
|
||||
|
||||
connection.processPacket(answer);
|
||||
} else {
|
||||
assertTrue("Expected to get a RosterPacket ", false);
|
||||
}
|
||||
|
||||
Roster roster = connection.getRoster();
|
||||
assertEquals("Size of roster", 1, roster.getEntries().size());
|
||||
RosterEntry entry = roster.getEntry(vaglafItem.getUser());
|
||||
assertNotNull("Roster contains vaglaf entry", entry);
|
||||
assertEquals("vaglaf entry in roster equals the sent entry", vaglafItem, RosterEntry.toRosterItem(entry));
|
||||
|
||||
RosterStore store = connection.getConfiguration().getRosterStore();
|
||||
assertEquals("Size of store", 1, store.getEntries().size());
|
||||
Item item = store.getEntry(vaglafItem.getUser());
|
||||
assertNotNull("Store contains vaglaf entry");
|
||||
assertEquals("vaglaf entry in store equals the sent entry", vaglafItem, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test roster versioning with roster pushes
|
||||
*/
|
||||
@Test(timeout = 5000)
|
||||
public void testRosterVersioningWithCachedRosterAndPushes() throws Throwable {
|
||||
connection.getRoster().reload();
|
||||
answerWithEmptyRosterResult();
|
||||
|
||||
RosterStore store = connection.getConfiguration().getRosterStore();
|
||||
Roster roster = connection.getRoster();
|
||||
|
||||
// Simulate a roster push adding vaglaf
|
||||
{
|
||||
RosterPacket rosterPush = new RosterPacket();
|
||||
rosterPush.setTo("rostertest@example.com/home");
|
||||
rosterPush.setType(Type.SET);
|
||||
rosterPush.setVersion("v97");
|
||||
|
||||
Item pushedItem = vaglafItem();
|
||||
rosterPush.addRosterItem(pushedItem);
|
||||
connection.processPacket(rosterPush);
|
||||
|
||||
assertEquals("Expect store version after push", "v97", store.getRosterVersion());
|
||||
|
||||
Item storedItem = store.getEntry("vaglaf@example.com");
|
||||
assertNotNull("Expect vaglaf to be added", storedItem);
|
||||
assertEquals("Expect vaglaf to be equal to pushed item", pushedItem, storedItem);
|
||||
|
||||
Collection<Item> rosterItems = new HashSet<Item>();
|
||||
for (RosterEntry entry : roster.getEntries()) {
|
||||
rosterItems.add(RosterEntry.toRosterItem(entry));
|
||||
}
|
||||
assertEquals(rosterItems, new HashSet<Item>(store.getEntries()));
|
||||
}
|
||||
|
||||
// Simulate a roster push removing vaglaf
|
||||
{
|
||||
RosterPacket rosterPush = new RosterPacket();
|
||||
rosterPush.setTo("rostertest@example.com/home");
|
||||
rosterPush.setType(Type.SET);
|
||||
rosterPush.setVersion("v98");
|
||||
|
||||
Item item = new Item("vaglaf@example.com", "vaglaf the only");
|
||||
item.setItemType(ItemType.remove);
|
||||
rosterPush.addRosterItem(item);
|
||||
connection.processPacket(rosterPush);
|
||||
|
||||
assertNull("Store doses not contain vaglaf", store.getEntry("vaglaf@example.com"));
|
||||
assertEquals("Expect store version after push", "v98", store.getRosterVersion());
|
||||
}
|
||||
}
|
||||
|
||||
private Item vaglafItem() {
|
||||
Item item = new Item("vaglaf@example.com", "vaglaf the only");
|
||||
item.setItemType(ItemType.both);
|
||||
item.addGroupName("all");
|
||||
item.addGroupName("friends");
|
||||
item.addGroupName("partners");
|
||||
return item;
|
||||
}
|
||||
|
||||
private void populateStore(RosterStore store) throws IOException {
|
||||
store.addEntry(new RosterPacket.Item("geoff@example.com", "geoff hurley"), "");
|
||||
|
||||
RosterPacket.Item item = new RosterPacket.Item("joe@example.com", "joe stevens");
|
||||
item.addGroupName("friends");
|
||||
item.addGroupName("partners");
|
||||
store.addEntry(item, "");
|
||||
|
||||
item = new RosterPacket.Item("higgins@example.com", "higgins mcmann");
|
||||
item.addGroupName("all");
|
||||
item.addGroupName("friends");
|
||||
store.addEntry(item, "v96");
|
||||
}
|
||||
|
||||
private void answerWithEmptyRosterResult() throws InterruptedException {
|
||||
// We expect that the roster request is the only packet sent. This is not part of the specification,
|
||||
// but a shortcut in the test implementation.
|
||||
Packet sentPacket = connection.getSentPacket();
|
||||
if (sentPacket instanceof RosterPacket) {
|
||||
final IQ emptyIQ = IQ.createResultIQ((RosterPacket)sentPacket);
|
||||
connection.processPacket(emptyIQ);
|
||||
} else {
|
||||
assertTrue("Expected to get a RosterPacket ", false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue