Skip to content

Commit 9d063fb

Browse files
committed
feat: Add comprehensive Timeline Preview integration test coverage
- Implement Timeline Preview API integration tests with real preview_timestamp and release_id scenarios - Add authentication flow tests for Management Token vs Preview Token scenarios - Create enhanced mock infrastructure for Timeline Preview API simulation - Fix permissive error handling in existing integration tests with specific error assertions - Add performance and load testing for Timeline Preview operations - Implement cache validation tests against live API responses - Create comprehensive error scenario tests including HTTP status mapping New test suites: - TimelinePreviewApiTests: Core Timeline API functionality - TimelineAuthenticationTests: Authentication flow validation - TimelineCacheValidationTests: Cache behavior and fingerprinting - TimelinePreviewErrorTests: Comprehensive error handling - TimelinePreviewPerformanceTest: Performance benchmarking - LivePreviewLoadTest: Concurrent operation testing Test coverage includes: - Timeline queries with preview_timestamp and release_id headers - Fork isolation for concurrent timeline states - Cache hit performance validation (3x+ speedup) - Memory leak detection and prevention - Network error handling and graceful degradation - Authentication token validation scenarios - HTTP status code mapping to SDK exceptions Fixes integration test reliability by replacing broad exception catching with specific error assertions and proper test isolation.
1 parent c4e7c15 commit 9d063fb

14 files changed

Lines changed: 6000 additions & 24 deletions
Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using AutoFixture;
5+
using Contentstack.Core.Configuration;
6+
using Contentstack.Core.Unit.Tests.Helpers;
7+
using Contentstack.Core.Unit.Tests.Mokes;
8+
using Xunit;
9+
10+
namespace Contentstack.Core.Unit.Tests
11+
{
12+
/// <summary>
13+
/// Unit tests for ContentstackClient.Fork() method
14+
/// Tests client forking behavior, isolation, and configuration preservation
15+
/// </summary>
16+
[Trait("Category", "TimelinePreview")]
17+
[Trait("Category", "Fork")]
18+
public class ContentstackClientForkTests : ContentstackClientTestBase
19+
{
20+
#region Positive Test Cases
21+
22+
[Fact]
23+
public void Fork_CreatesIndependentClientInstance()
24+
{
25+
// Arrange
26+
var parentClient = CreateClientWithTimeline();
27+
28+
// Act
29+
var forkedClient = parentClient.Fork();
30+
31+
// Assert
32+
Assert.NotNull(forkedClient);
33+
AssertClientsAreIndependent(parentClient, forkedClient);
34+
}
35+
36+
[Fact]
37+
public void Fork_PreservesBaseConfiguration()
38+
{
39+
// Arrange
40+
var parentClient = CreateClientWithTimeline();
41+
42+
// Act
43+
var forkedClient = parentClient.Fork();
44+
45+
// Assert
46+
AssertConfigurationPreserved(parentClient, forkedClient);
47+
}
48+
49+
[Fact]
50+
public void Fork_PreservesCustomHeaders()
51+
{
52+
// Arrange
53+
var parentClient = CreateClientWithTimeline();
54+
var customHeaderKey = "X-Custom-Header";
55+
var customHeaderValue = _fixture.Create<string>();
56+
57+
parentClient.SetHeader(customHeaderKey, customHeaderValue);
58+
59+
// Act
60+
var forkedClient = parentClient.Fork();
61+
62+
// Assert
63+
// Verify custom header is preserved in forked client
64+
var parentHeaders = GetInternalField<Dictionary<string, object>>(parentClient, "_LocalHeaders");
65+
var forkedHeaders = GetInternalField<Dictionary<string, object>>(forkedClient, "_LocalHeaders");
66+
67+
Assert.True(parentHeaders.ContainsKey(customHeaderKey));
68+
Assert.True(forkedHeaders.ContainsKey(customHeaderKey));
69+
Assert.Equal(parentHeaders[customHeaderKey], forkedHeaders[customHeaderKey]);
70+
}
71+
72+
[Fact]
73+
public void Fork_CopiesContentTypeUidHints()
74+
{
75+
// Arrange
76+
var parentClient = CreateClientWithTimeline();
77+
var contentTypeUid = _fixture.Create<string>();
78+
var entryUid = _fixture.Create<string>();
79+
80+
// Set content type and entry hints on parent
81+
SetInternalField(parentClient, "currentContenttypeUid", contentTypeUid);
82+
SetInternalField(parentClient, "currentEntryUid", entryUid);
83+
84+
// Act
85+
var forkedClient = parentClient.Fork();
86+
87+
// Assert
88+
var forkedContentTypeUid = GetInternalField<string>(forkedClient, "currentContenttypeUid");
89+
var forkedEntryUid = GetInternalField<string>(forkedClient, "currentEntryUid");
90+
91+
Assert.Equal(contentTypeUid, forkedContentTypeUid);
92+
Assert.Equal(entryUid, forkedEntryUid);
93+
}
94+
95+
[Fact]
96+
public void Fork_IndependentLivePreviewConfig()
97+
{
98+
// Arrange
99+
var parentClient = CreateClientWithTimeline();
100+
var parentConfig = parentClient.GetLivePreviewConfig();
101+
parentConfig.PreviewTimestamp = "2024-11-29T14:30:00.000Z";
102+
103+
// Act
104+
var forkedClient = parentClient.Fork();
105+
var forkedConfig = forkedClient.GetLivePreviewConfig();
106+
107+
// Assert - Configs are independent instances
108+
Assert.NotSame(parentConfig, forkedConfig);
109+
110+
// Assert - Initial values are copied
111+
Assert.Equal(parentConfig.PreviewTimestamp, forkedConfig.PreviewTimestamp);
112+
Assert.Equal(parentConfig.ReleaseId, forkedConfig.ReleaseId);
113+
Assert.Equal(parentConfig.Enable, forkedConfig.Enable);
114+
Assert.Equal(parentConfig.Host, forkedConfig.Host);
115+
Assert.Equal(parentConfig.ManagementToken, forkedConfig.ManagementToken);
116+
}
117+
118+
[Fact]
119+
public void Fork_SharedConfigurationReference()
120+
{
121+
// Arrange
122+
var parentClient = CreateClientWithTimeline();
123+
var parentConfig = parentClient.GetLivePreviewConfig();
124+
parentConfig.PreviewResponse = CreateMockPreviewResponse();
125+
126+
// Act
127+
var forkedClient = parentClient.Fork();
128+
var forkedConfig = forkedClient.GetLivePreviewConfig();
129+
130+
// Assert - PreviewResponse is shared reference (memory efficient)
131+
Assert.Same(parentConfig.PreviewResponse, forkedConfig.PreviewResponse);
132+
}
133+
134+
[Fact]
135+
public void Fork_MultipleLevels_IndependentContexts()
136+
{
137+
// Arrange
138+
var level1Client = CreateClientWithTimeline();
139+
level1Client.GetLivePreviewConfig().PreviewTimestamp = "2024-11-29T10:00:00.000Z";
140+
141+
// Act
142+
var level2Client = level1Client.Fork();
143+
level2Client.GetLivePreviewConfig().PreviewTimestamp = "2024-11-29T12:00:00.000Z";
144+
145+
var level3Client = level2Client.Fork();
146+
level3Client.GetLivePreviewConfig().PreviewTimestamp = "2024-11-29T14:00:00.000Z";
147+
148+
// Assert - Each level maintains its own timeline
149+
Assert.Equal("2024-11-29T10:00:00.000Z", level1Client.GetLivePreviewConfig().PreviewTimestamp);
150+
Assert.Equal("2024-11-29T12:00:00.000Z", level2Client.GetLivePreviewConfig().PreviewTimestamp);
151+
Assert.Equal("2024-11-29T14:00:00.000Z", level3Client.GetLivePreviewConfig().PreviewTimestamp);
152+
153+
// Assert - All are independent instances
154+
AssertClientsAreIndependent(level1Client, level2Client);
155+
AssertClientsAreIndependent(level2Client, level3Client);
156+
AssertClientsAreIndependent(level1Client, level3Client);
157+
}
158+
159+
[Fact]
160+
public void Fork_ParallelModifications_IsolatedChanges()
161+
{
162+
// Arrange
163+
var parentClient = CreateClientWithTimeline();
164+
var fork1 = parentClient.Fork();
165+
var fork2 = parentClient.Fork();
166+
167+
// Act - Modify each fork independently
168+
fork1.GetLivePreviewConfig().PreviewTimestamp = "2024-11-29T08:00:00.000Z";
169+
fork1.GetLivePreviewConfig().ReleaseId = "fork1_release";
170+
171+
fork2.GetLivePreviewConfig().PreviewTimestamp = "2024-11-29T16:00:00.000Z";
172+
fork2.GetLivePreviewConfig().ReleaseId = "fork2_release";
173+
174+
// Assert - Changes are isolated
175+
Assert.Equal("2024-11-29T08:00:00.000Z", fork1.GetLivePreviewConfig().PreviewTimestamp);
176+
Assert.Equal("fork1_release", fork1.GetLivePreviewConfig().ReleaseId);
177+
178+
Assert.Equal("2024-11-29T16:00:00.000Z", fork2.GetLivePreviewConfig().PreviewTimestamp);
179+
Assert.Equal("fork2_release", fork2.GetLivePreviewConfig().ReleaseId);
180+
181+
// Parent client should maintain its original state
182+
Assert.Equal("2024-11-29T14:30:00.000Z", parentClient.GetLivePreviewConfig().PreviewTimestamp);
183+
Assert.Equal("test_release_123", parentClient.GetLivePreviewConfig().ReleaseId);
184+
}
185+
186+
[Fact]
187+
public void Fork_PreservesPlugins()
188+
{
189+
// Arrange
190+
var parentClient = CreateClientWithTimeline();
191+
var mockHandler = new TimelineMockHttpHandler().ForSuccessfulLivePreview();
192+
parentClient.Plugins.Add(mockHandler);
193+
194+
// Act
195+
var forkedClient = parentClient.Fork();
196+
197+
// Assert - Plugins collection exists but fork doesn't share plugin instances
198+
Assert.NotNull(forkedClient.Plugins);
199+
Assert.Empty(forkedClient.Plugins); // Fork starts with empty plugins (isolated)
200+
}
201+
202+
[Fact]
203+
public async Task Fork_IndependentLivePreviewOperations()
204+
{
205+
// Arrange
206+
var parentClient = CreateClientWithLivePreview();
207+
var fork1 = parentClient.Fork();
208+
var fork2 = parentClient.Fork();
209+
210+
// Set up different timeline contexts
211+
var query1 = CreateLivePreviewQuery(previewTimestamp: "2024-11-29T08:00:00.000Z");
212+
var query2 = CreateLivePreviewQuery(previewTimestamp: "2024-11-29T16:00:00.000Z");
213+
214+
// Act
215+
await fork1.LivePreviewQueryAsync(query1);
216+
await fork2.LivePreviewQueryAsync(query2);
217+
218+
// Assert - Each fork maintains its own timeline context
219+
Assert.Equal("2024-11-29T08:00:00.000Z", fork1.GetLivePreviewConfig().PreviewTimestamp);
220+
Assert.Equal("2024-11-29T16:00:00.000Z", fork2.GetLivePreviewConfig().PreviewTimestamp);
221+
222+
// Parent should be unaffected
223+
Assert.Null(parentClient.GetLivePreviewConfig().PreviewTimestamp);
224+
}
225+
226+
#endregion
227+
228+
#region Negative Test Cases
229+
230+
[Fact]
231+
public void Fork_WithNullLivePreviewConfig_HandlesGracefully()
232+
{
233+
// Arrange
234+
var parentClient = CreateClient(); // Client without LivePreview
235+
SetInternalProperty(parentClient, "LivePreviewConfig", null);
236+
237+
// Act & Assert - Should not throw
238+
var forkedClient = parentClient.Fork();
239+
240+
Assert.NotNull(forkedClient);
241+
// Verify the fork handles null config appropriately
242+
var forkedConfig = forkedClient.GetLivePreviewConfig();
243+
Assert.NotNull(forkedConfig); // Should create a new config if parent was null
244+
}
245+
246+
[Fact]
247+
public void Fork_WithCorruptedConfiguration_CreatesValidFork()
248+
{
249+
// Arrange
250+
var parentClient = CreateClientWithTimeline();
251+
var config = parentClient.GetLivePreviewConfig();
252+
253+
// Corrupt some configuration properties
254+
config.Host = null;
255+
config.PreviewTimestamp = "invalid-timestamp-format";
256+
257+
// Act & Assert - Should not throw
258+
var forkedClient = parentClient.Fork();
259+
var forkedConfig = forkedClient.GetLivePreviewConfig();
260+
261+
Assert.NotNull(forkedClient);
262+
Assert.NotNull(forkedConfig);
263+
Assert.Equal("invalid-timestamp-format", forkedConfig.PreviewTimestamp); // Corruption is copied but doesn't break fork
264+
}
265+
266+
[Fact]
267+
public void Fork_AfterParentModification_IsolatesChanges()
268+
{
269+
// Arrange
270+
var parentClient = CreateClientWithTimeline();
271+
var originalTimestamp = parentClient.GetLivePreviewConfig().PreviewTimestamp;
272+
273+
// Act - Create fork, then modify parent
274+
var forkedClient = parentClient.Fork();
275+
parentClient.GetLivePreviewConfig().PreviewTimestamp = "2024-12-01T00:00:00.000Z";
276+
277+
// Assert - Fork maintains original timestamp
278+
Assert.Equal(originalTimestamp, forkedClient.GetLivePreviewConfig().PreviewTimestamp);
279+
Assert.Equal("2024-12-01T00:00:00.000Z", parentClient.GetLivePreviewConfig().PreviewTimestamp);
280+
}
281+
282+
[Fact]
283+
public void Fork_WithLargeNumberOfForks_MaintainsPerformance()
284+
{
285+
// Arrange
286+
var parentClient = CreateClientWithTimeline();
287+
var numberOfForks = 1000;
288+
var forks = new ContentstackClient[numberOfForks];
289+
290+
// Act - Measure fork creation time
291+
var startTime = DateTime.UtcNow;
292+
293+
for (int i = 0; i < numberOfForks; i++)
294+
{
295+
forks[i] = parentClient.Fork();
296+
forks[i].GetLivePreviewConfig().PreviewTimestamp = $"2024-11-{(i % 12) + 1:D2}-01T00:00:00.000Z";
297+
}
298+
299+
var duration = DateTime.UtcNow - startTime;
300+
301+
// Assert - Fork creation should be fast (under 1 second for 1000 forks)
302+
Assert.True(duration.TotalSeconds < 1.0, $"Fork creation took {duration.TotalMilliseconds}ms for {numberOfForks} forks");
303+
304+
// Verify all forks are independent
305+
for (int i = 0; i < Math.Min(10, numberOfForks); i++) // Check first 10 for performance
306+
{
307+
AssertClientsAreIndependent(parentClient, forks[i]);
308+
}
309+
}
310+
311+
#endregion
312+
313+
#region Edge Cases
314+
315+
[Fact]
316+
public void Fork_WithEmptyHeaders_HandlesCorrectly()
317+
{
318+
// Arrange
319+
var parentClient = CreateClientWithTimeline();
320+
SetInternalField(parentClient, "_LocalHeaders", new Dictionary<string, object>());
321+
322+
// Act
323+
var forkedClient = parentClient.Fork();
324+
325+
// Assert
326+
Assert.NotNull(forkedClient);
327+
var forkedHeaders = GetInternalField<Dictionary<string, object>>(forkedClient, "_LocalHeaders");
328+
Assert.NotNull(forkedHeaders);
329+
}
330+
331+
[Fact]
332+
public void Fork_WithNullHeaders_HandlesCorrectly()
333+
{
334+
// Arrange
335+
var parentClient = CreateClientWithTimeline();
336+
SetInternalField(parentClient, "_LocalHeaders", null);
337+
338+
// Act & Assert - Should not throw
339+
var forkedClient = parentClient.Fork();
340+
Assert.NotNull(forkedClient);
341+
}
342+
343+
[Fact]
344+
public void Fork_RecursiveForkModification_MaintainsIsolation()
345+
{
346+
// Arrange
347+
var level1 = CreateClientWithTimeline();
348+
level1.GetLivePreviewConfig().ReleaseId = "level1_release";
349+
350+
var level2 = level1.Fork();
351+
level2.GetLivePreviewConfig().ReleaseId = "level2_release";
352+
353+
var level3 = level2.Fork();
354+
level3.GetLivePreviewConfig().ReleaseId = "level3_release";
355+
356+
// Act - Modify level2 after level3 is created
357+
level2.GetLivePreviewConfig().PreviewTimestamp = "2024-11-30T00:00:00.000Z";
358+
359+
// Assert - Level3 should not be affected by level2 changes
360+
Assert.Equal("level1_release", level1.GetLivePreviewConfig().ReleaseId);
361+
Assert.Equal("level2_release", level2.GetLivePreviewConfig().ReleaseId);
362+
Assert.Equal("level3_release", level3.GetLivePreviewConfig().ReleaseId);
363+
364+
Assert.Equal("2024-11-30T00:00:00.000Z", level2.GetLivePreviewConfig().PreviewTimestamp);
365+
Assert.NotEqual("2024-11-30T00:00:00.000Z", level3.GetLivePreviewConfig().PreviewTimestamp);
366+
}
367+
368+
#endregion
369+
}
370+
}

0 commit comments

Comments
 (0)