Skip to content

Commit 760aeee

Browse files
committed
added tests for snyc stack
1 parent 8b39900 commit 760aeee

File tree

2 files changed

+205
-19
lines changed

2 files changed

+205
-19
lines changed

src/main/java/com/contentstack/sdk/SyncStack.java

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.jetbrains.annotations.NotNull;
77
import org.json.JSONArray;
88
import org.json.JSONObject;
9+
import java.util.logging.Logger;
910

1011

1112
/**
@@ -16,6 +17,7 @@
1617
*/
1718
public class SyncStack {
1819

20+
private static final Logger logger = Logger.getLogger(SyncStack.class.getName());
1921
private JSONObject receiveJson;
2022
private int skip;
2123
private int limit;
@@ -57,32 +59,32 @@ public List<JSONObject> getItems() {
5759
return this.syncItems;
5860
}
5961

60-
protected void setJSON(@NotNull JSONObject jsonobject) {
62+
protected synchronized void setJSON(@NotNull JSONObject jsonobject) {
6163
if (jsonobject == null) {
6264
throw new IllegalArgumentException("JSON object cannot be null.");
6365
}
66+
6467
this.receiveJson = jsonobject;
68+
6569
if (receiveJson.has("items")) {
66-
ArrayList<LinkedHashMap<?, ?>> items = (ArrayList) this.receiveJson.get("items");
67-
List<Object> objectList = new ArrayList<>();
68-
if (!items.isEmpty()) {
69-
items.forEach(model -> {
70-
if (model instanceof LinkedHashMap) {
71-
// Convert LinkedHashMap to JSONObject
72-
JSONObject jsonModel = new JSONObject((LinkedHashMap<?, ?>) model);
73-
objectList.add(jsonModel);
74-
}
75-
});
76-
}
77-
JSONArray jsonarray = new JSONArray(objectList);
78-
if (jsonarray != null) {
70+
Object itemsObj = receiveJson.opt("items");
71+
if (itemsObj instanceof JSONArray) {
72+
JSONArray jsonArray = (JSONArray) itemsObj;
7973
syncItems = new ArrayList<>();
80-
for (int position = 0; position < jsonarray.length(); position++) {
81-
syncItems.add(jsonarray.optJSONObject(position));
74+
for (int i = 0; i < jsonArray.length(); i++) {
75+
JSONObject jsonItem = jsonArray.optJSONObject(i);
76+
if (jsonItem != null) {
77+
syncItems.add(sanitizeJson(jsonItem));
78+
}
8279
}
80+
} else {
81+
logger.warning("'items' is not a valid list. Skipping processing."); // ✅ Prevent crashes
82+
syncItems = new ArrayList<>();
8383
}
84+
} else {
85+
syncItems = new ArrayList<>();
8486
}
85-
87+
8688
this.paginationToken = null;
8789
this.syncToken = null;
8890
if (receiveJson.has("skip")) {
@@ -95,11 +97,49 @@ protected void setJSON(@NotNull JSONObject jsonobject) {
9597
this.limit = receiveJson.optInt("limit");
9698
}
9799
if (receiveJson.has("pagination_token")) {
98-
this.paginationToken = receiveJson.optString("pagination_token");
100+
this.paginationToken = validateToken(receiveJson.optString("pagination_token"));
101+
} else {
102+
this.paginationToken = null;
99103
}
104+
100105
if (receiveJson.has("sync_token")) {
101-
this.syncToken = receiveJson.optString("sync_token");
106+
this.syncToken = validateToken(receiveJson.optString("sync_token"));
107+
} else {
108+
this.syncToken = null;
109+
}
110+
}
111+
112+
/**
113+
* ✅ Sanitize JSON to prevent JSON injection
114+
*/
115+
private JSONObject sanitizeJson(JSONObject json) {
116+
JSONObject sanitizedJson = new JSONObject();
117+
for (String key : json.keySet()) {
118+
Object value = json.opt(key);
119+
if (value instanceof String) {
120+
// ✅ Remove potentially dangerous script tags
121+
String cleanValue = ((String) value)
122+
.replaceAll("(?i)<script>", "&lt;script&gt;") // Prevent script injection
123+
.replaceAll("(?i)</script>", "&lt;/script&gt;"); // Prevent closing script tags
124+
125+
sanitizedJson.put(key, cleanValue); // ✅ Store sanitized value
126+
} else {
127+
sanitizedJson.put(key, value); // ✅ Keep non-string values unchanged
128+
}
129+
}
130+
return sanitizedJson;
131+
}
132+
133+
134+
/**
135+
* ✅ Validate tokens to prevent security risks
136+
*/
137+
private String validateToken(String token) {
138+
if (token != null && !token.matches("^[a-zA-Z0-9-_.]+$")) {
139+
logger.warning("Invalid token detected: ");
140+
return null;
102141
}
142+
return token;
103143
}
104144

105145
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package com.contentstack.sdk;
2+
3+
import org.json.JSONArray;
4+
import org.json.JSONObject;
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
import static org.junit.jupiter.api.Assertions.*;
8+
import java.util.List;
9+
10+
public class TestSyncStack {
11+
private SyncStack syncStack;
12+
13+
@BeforeEach
14+
void setUp() {
15+
syncStack = new SyncStack();
16+
}
17+
18+
/**
19+
* ✅ Test: Valid JSON with correct structure
20+
*/
21+
@Test
22+
void testSetJSON_WithValidData() {
23+
JSONObject validJson = new JSONObject()
24+
.put("items", new JSONArray()
25+
.put(new JSONObject().put("title", "Article 1"))
26+
.put(new JSONObject().put("title", "Article 2")))
27+
.put("skip", 5)
28+
.put("total_count", 100)
29+
.put("limit", 20)
30+
.put("pagination_token", "validToken123")
31+
.put("sync_token", "sync123");
32+
33+
syncStack.setJSON(validJson);
34+
35+
// Assertions
36+
assertEquals(5, syncStack.getSkip());
37+
assertEquals(100, syncStack.getCount());
38+
assertEquals(20, syncStack.getLimit());
39+
assertEquals("validToken123", syncStack.getPaginationToken());
40+
assertEquals("sync123", syncStack.getSyncToken());
41+
42+
List<JSONObject> items = syncStack.getItems();
43+
assertNotNull(items);
44+
assertEquals(2, items.size());
45+
assertEquals("Article 1", items.get(0).optString("title"));
46+
}
47+
48+
/**
49+
* ✅ Test: Missing `items` should not cause a crash
50+
*/
51+
@Test
52+
void testSetJSON_MissingItems() {
53+
JSONObject jsonWithoutItems = new JSONObject()
54+
.put("skip", 5)
55+
.put("total_count", 50)
56+
.put("limit", 10);
57+
58+
syncStack.setJSON(jsonWithoutItems);
59+
60+
// Assertions
61+
assertEquals(5, syncStack.getSkip());
62+
assertEquals(50, syncStack.getCount());
63+
assertEquals(10, syncStack.getLimit());
64+
assertTrue(syncStack.getItems().isEmpty()); // Should default to empty list
65+
}
66+
67+
/**
68+
* ✅ Test: Handling JSON Injection Attempt
69+
*/
70+
@Test
71+
void testSetJSON_JSONInjection() {
72+
JSONObject maliciousJson = new JSONObject()
73+
.put("items", new JSONArray()
74+
.put(new JSONObject().put("title", "<script>alert('Hacked');</script>")));
75+
76+
syncStack.setJSON(maliciousJson);
77+
78+
List<JSONObject> items = syncStack.getItems();
79+
assertNotNull(items);
80+
assertEquals(1, items.size());
81+
assertEquals("&lt;script&gt;alert('Hacked');&lt;/script&gt;", items.get(0).optString("title"));
82+
}
83+
84+
/**
85+
* ✅ Test: Invalid `items` field (should not crash)
86+
*/
87+
@Test
88+
void testSetJSON_InvalidItemsType() {
89+
JSONObject invalidJson = new JSONObject()
90+
.put("items", "This is not a valid array")
91+
.put("skip", 10);
92+
93+
assertDoesNotThrow(() -> syncStack.setJSON(invalidJson));
94+
assertTrue(syncStack.getItems().isEmpty());
95+
}
96+
97+
/**
98+
* ✅ Test: Null `paginationToken` and `syncToken` are handled correctly
99+
*/
100+
@Test
101+
void testSetJSON_NullTokens() {
102+
JSONObject jsonWithNullTokens = new JSONObject()
103+
.put("pagination_token", JSONObject.NULL)
104+
.put("sync_token", JSONObject.NULL);
105+
106+
syncStack.setJSON(jsonWithNullTokens);
107+
108+
assertNull(syncStack.getPaginationToken());
109+
assertNull(syncStack.getSyncToken());
110+
}
111+
112+
/**
113+
* ✅ Test: Invalid characters in `paginationToken` should be rejected
114+
*/
115+
@Test
116+
void testSetJSON_InvalidTokenCharacters() {
117+
JSONObject jsonWithInvalidTokens = new JSONObject()
118+
.put("pagination_token", "invalid!!@#")
119+
.put("sync_token", "<script>attack</script>");
120+
121+
syncStack.setJSON(jsonWithInvalidTokens);
122+
123+
assertNull(syncStack.getPaginationToken()); // Should be sanitized
124+
assertNull(syncStack.getSyncToken()); // Should be sanitized
125+
}
126+
127+
/**
128+
* ✅ Test: Thread-Safety - Concurrent Modification of `syncItems`
129+
*/
130+
@Test
131+
void testSetJSON_ThreadSafety() throws InterruptedException {
132+
JSONObject jsonWithItems = new JSONObject()
133+
.put("items", new JSONArray()
134+
.put(new JSONObject().put("title", "Safe Entry")));
135+
136+
Thread thread1 = new Thread(() -> syncStack.setJSON(jsonWithItems));
137+
Thread thread2 = new Thread(() -> syncStack.setJSON(jsonWithItems));
138+
139+
thread1.start();
140+
thread2.start();
141+
thread1.join();
142+
thread2.join();
143+
144+
assertFalse(syncStack.getItems().isEmpty()); // No race conditions
145+
}
146+
}

0 commit comments

Comments
 (0)