1package eu.siacs.conversations.persistance;
2
3import android.content.ContentValues;
4import android.database.sqlite.SQLiteDatabase;
5import androidx.test.core.app.ApplicationProvider;
6import androidx.test.ext.junit.runners.AndroidJUnit4;
7
8import org.json.JSONException;
9import org.json.JSONObject;
10import org.junit.After;
11import org.junit.Assert;
12import org.junit.Before;
13import org.junit.BeforeClass;
14import org.junit.Test;
15import org.junit.runner.RunWith;
16
17import java.util.HashMap;
18import java.util.UUID;
19
20import eu.siacs.conversations.entities.Account;
21import eu.siacs.conversations.entities.Conversation;
22import eu.siacs.conversations.entities.MucOptions;
23import eu.siacs.conversations.xml.Namespace;
24import im.conversations.android.xmpp.model.disco.info.Feature;
25import im.conversations.android.xmpp.model.disco.info.InfoQuery;
26
27@RunWith(AndroidJUnit4.class)
28public class DatabaseBackendTest {
29 private record AccountFixture(String uuid, String username, String server) {
30 void write(DatabaseBackend db) {
31 final var cv = new ContentValues();
32 cv.put("uuid", uuid);
33 cv.put("username", username);
34 cv.put("server", server);
35 cv.put("password", "test");
36 cv.put("options", 0);
37 db.getWritableDatabase().insertWithOnConflict(
38 "accounts", null, cv, SQLiteDatabase.CONFLICT_REPLACE);
39 }
40 }
41
42 private record ConversationFixture(
43 String conversationUuid,
44 AccountFixture account,
45 String name,
46 String contactJid,
47 String attributes,
48 HashMap<MucOptions.User.OccupantId, MucOptions.User.CacheEntry> occupantCache
49 ) {
50 void writeConversation(DatabaseBackend db) {
51 final var cv = new ContentValues();
52 cv.put("uuid", conversationUuid);
53 cv.put("name", name);
54 cv.put("contactUuid", "");
55 cv.put("accountUuid", account.uuid());
56 cv.put("contactJid", contactJid);
57 cv.put("created", System.currentTimeMillis());
58 cv.put("status", Conversation.STATUS_AVAILABLE);
59 cv.put("mode", Conversation.MODE_MULTI);
60 cv.put("attributes", attributes);
61 db.getWritableDatabase().insert("conversations", null, cv);
62 }
63
64 void writeOccupants(DatabaseBackend db) {
65 for (final var entry : occupantCache.entrySet()) {
66 final var cv = new ContentValues();
67 cv.put(MucOptions.User.CacheEntry.OCCUPANT_ID, entry.getKey().inner());
68 cv.put(MucOptions.User.CacheEntry.CONVERSATION_UUID, conversationUuid);
69 cv.put(MucOptions.User.CacheEntry.AVATAR, entry.getValue().avatar());
70 cv.put(MucOptions.User.CacheEntry.NICK, entry.getValue().nick());
71 db.getWritableDatabase().insert(
72 MucOptions.User.CacheEntry.TABLENAME, null, cv);
73 }
74 }
75
76 void writeAll(DatabaseBackend db) {
77 writeConversation(db);
78 writeOccupants(db);
79 }
80
81 Conversation extractAndConfigure(DatabaseBackend db)
82 {
83 final var conversations = db.getConversations(Conversation.STATUS_AVAILABLE);
84 Assert.assertNotNull("getConversations should not return null", conversations);
85
86 Conversation match = null;
87 for (final var c : conversations) {
88 if (conversationUuid.equals(c.getUuid())) {
89 match = c;
90 break;
91 }
92 }
93 Assert.assertNotNull(
94 "Fixture conversation " + conversationUuid + " not found", match);
95
96 match.setAccount(db.getAccounts().get(0));
97 match.getMucOptions().updateConfiguration(INFO_QUERY_WITH_OCCUPANT_ID);
98 match.putAllInMucOccupantCache(db.getMucUsersForConversation(match));
99 return match;
100 }
101 }
102
103 private DatabaseBackend db;
104 private static final InfoQuery INFO_QUERY_WITH_OCCUPANT_ID = new InfoQuery();
105
106 private static AccountFixture ACCOUNT;
107 private static ConversationFixture CONFORMING;
108 private static ConversationFixture NO_CACHED_MUC_USERS;
109 private static ConversationFixture[] FIXTURES;
110
111 @BeforeClass
112 public static void setupClass() throws JSONException {
113 final var occupantIdFeature = new Feature();
114 occupantIdFeature.setVar(Namespace.OCCUPANT_ID);
115 INFO_QUERY_WITH_OCCUPANT_ID.addChild(occupantIdFeature);
116
117 ACCOUNT = new AccountFixture(
118 UUID.randomUUID().toString(), "test", "example.com");
119
120 final var conformingCache =
121 new HashMap<MucOptions.User.OccupantId, MucOptions.User.CacheEntry>();
122 conformingCache.put(
123 new MucOptions.User.OccupantId(UUID.randomUUID().toString()),
124 new MucOptions.User.CacheEntry(UUID.randomUUID().toString(), "ConformingUser"));
125
126 CONFORMING = new ConversationFixture(
127 UUID.randomUUID().toString(),
128 ACCOUNT,
129 "Normal MUC",
130 "normalroom@conference.example.com",
131 new JSONObject().put("mucNick", "testMucNick").toString(),
132 conformingCache
133 );
134
135 NO_CACHED_MUC_USERS = new ConversationFixture(
136 UUID.randomUUID().toString(),
137 ACCOUNT,
138 "Empty Cache MUC",
139 "emptycache@conference.example.com",
140 new JSONObject().put("mucNick", "testMucNick").toString(),
141 new HashMap<>()
142 );
143
144 FIXTURES = new ConversationFixture[] { CONFORMING, NO_CACHED_MUC_USERS };
145 }
146
147 @Before
148 public void setUp() throws Exception {
149 db = DatabaseBackend.getInstance(
150 ApplicationProvider.getApplicationContext());
151 ACCOUNT.write(db);
152 for (final var fixture : FIXTURES) {
153 fixture.writeAll(db);
154 }
155 }
156
157 @After
158 public void tearDown() {
159 SQLiteDatabase sqDb = db.getWritableDatabase();
160 sqDb.delete(Conversation.TABLENAME, null, null);
161 sqDb.delete(Account.TABLENAME, null, null);
162 sqDb.delete(MucOptions.User.CacheEntry.TABLENAME, null, null);
163 }
164
165 @Test
166 public void getConversationsCorrectlyReadsMucUsers() throws Exception {
167 Assert.assertTrue(
168 "Occupant cache should be empty when no occupants are written",
169 NO_CACHED_MUC_USERS
170 .extractAndConfigure(db)
171 .getMucOccupantCache()
172 .isEmpty()
173 );
174
175 Assert.assertEquals(
176 "Cached entries should match fixture",
177 CONFORMING
178 .extractAndConfigure(db)
179 .getMucOccupantCache(),
180 CONFORMING.occupantCache()
181 );
182 }
183
184 @Test
185 public void updateConversationWritesMucOccupantsCache() throws Exception {
186 final var conversation = NO_CACHED_MUC_USERS.extractAndConfigure(db);
187 conversation.putAllInMucOccupantCache(CONFORMING.occupantCache());
188 db.updateConversation(conversation);
189
190 final var readBackCache = db.getMucUsersForConversation(conversation);
191 Assert.assertEquals(
192 "Cache should match after updateConversation",
193 CONFORMING.occupantCache(),
194 readBackCache
195 );
196 }
197}