Skip to content

Commit 8671e06

Browse files
committed
Merge branch 'development' into feat/Oauth
2 parents 5cacd08 + 905e558 commit 8671e06

File tree

11 files changed

+1028
-15
lines changed

11 files changed

+1028
-15
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
# Changelog
22
## [v0.4.0](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.4.0)
3-
- Feature
3+
- Feat
4+
- **MFA Support**: Added Multi-Factor Authentication (MFA) support for login operations
5+
- Added `mfaSecret` parameter to `Login` and `LoginAsync` methods for TOTP generation
6+
- Automatic TOTP token generation from Base32-encoded MFA secrets using Otp.NET library
7+
- Comprehensive test coverage for MFA functionality including unit and integration tests
8+
- Supports both explicit token and MFA secret-based authentication flows
49
- Added Support for OAuth
10+
- Added Comprehensive test coverage for OAuth Functionality in Unit Test cases.
11+
- Supports both Login with and without OAuth Flows
512

613
## [v0.3.2](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.3.2)
714
- Fix

Contentstack.Management.Core.Tests/IntegrationTest/Contentstack001_LoginTest.cs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,123 @@ public void Test007_Should_Return_Loggedin_User_With_Organizations_detail()
175175
Assert.Fail(e.Message);
176176
}
177177
}
178+
179+
[TestMethod]
180+
[DoNotParallelize]
181+
public void Test008_Should_Fail_Login_With_Invalid_MfaSecret()
182+
{
183+
ContentstackClient client = new ContentstackClient();
184+
NetworkCredential credentials = new NetworkCredential("test_user", "test_password");
185+
string invalidMfaSecret = "INVALID_BASE32_SECRET!@#";
186+
187+
try
188+
{
189+
ContentstackResponse contentstackResponse = client.Login(credentials, null, invalidMfaSecret);
190+
Assert.Fail("Expected exception for invalid MFA secret");
191+
}
192+
catch (ArgumentException)
193+
{
194+
// Expected exception for invalid Base32 encoding
195+
Assert.IsTrue(true);
196+
}
197+
catch (Exception e)
198+
{
199+
Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
200+
}
201+
}
202+
203+
[TestMethod]
204+
[DoNotParallelize]
205+
public void Test009_Should_Generate_TOTP_Token_With_Valid_MfaSecret()
206+
{
207+
ContentstackClient client = new ContentstackClient();
208+
NetworkCredential credentials = new NetworkCredential("test_user", "test_password");
209+
string validMfaSecret = "JBSWY3DPEHPK3PXP"; // Valid Base32 test secret
210+
211+
try
212+
{
213+
// This should fail due to invalid credentials, but should succeed in generating TOTP
214+
ContentstackResponse contentstackResponse = client.Login(credentials, null, validMfaSecret);
215+
}
216+
catch (ContentstackErrorException errorException)
217+
{
218+
// Expected to fail due to invalid credentials, but we verify it processed the MFA secret
219+
// The error should be about credentials, not about MFA secret format
220+
Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode);
221+
Assert.IsTrue(errorException.Message.Contains("email or password") ||
222+
errorException.Message.Contains("credentials") ||
223+
errorException.Message.Contains("authentication"));
224+
}
225+
catch (ArgumentException)
226+
{
227+
Assert.Fail("Should not throw ArgumentException for valid MFA secret");
228+
}
229+
catch (Exception e)
230+
{
231+
Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
232+
}
233+
}
234+
235+
[TestMethod]
236+
[DoNotParallelize]
237+
public async System.Threading.Tasks.Task Test010_Should_Generate_TOTP_Token_With_Valid_MfaSecret_Async()
238+
{
239+
ContentstackClient client = new ContentstackClient();
240+
NetworkCredential credentials = new NetworkCredential("test_user", "test_password");
241+
string validMfaSecret = "JBSWY3DPEHPK3PXP"; // Valid Base32 test secret
242+
243+
try
244+
{
245+
// This should fail due to invalid credentials, but should succeed in generating TOTP
246+
ContentstackResponse contentstackResponse = await client.LoginAsync(credentials, null, validMfaSecret);
247+
}
248+
catch (ContentstackErrorException errorException)
249+
{
250+
// Expected to fail due to invalid credentials, but we verify it processed the MFA secret
251+
// The error should be about credentials, not about MFA secret format
252+
Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode);
253+
Assert.IsTrue(errorException.Message.Contains("email or password") ||
254+
errorException.Message.Contains("credentials") ||
255+
errorException.Message.Contains("authentication"));
256+
}
257+
catch (ArgumentException)
258+
{
259+
Assert.Fail("Should not throw ArgumentException for valid MFA secret");
260+
}
261+
catch (Exception e)
262+
{
263+
Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
264+
}
265+
}
266+
267+
[TestMethod]
268+
[DoNotParallelize]
269+
public void Test011_Should_Prefer_Explicit_Token_Over_MfaSecret()
270+
{
271+
ContentstackClient client = new ContentstackClient();
272+
NetworkCredential credentials = new NetworkCredential("test_user", "test_password");
273+
string validMfaSecret = "JBSWY3DPEHPK3PXP";
274+
string explicitToken = "123456";
275+
276+
try
277+
{
278+
// This should fail due to invalid credentials, but should use explicit token
279+
ContentstackResponse contentstackResponse = client.Login(credentials, explicitToken, validMfaSecret);
280+
}
281+
catch (ContentstackErrorException errorException)
282+
{
283+
// Expected to fail due to invalid credentials
284+
// The important thing is that it didn't throw an exception about MFA secret processing
285+
Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode);
286+
}
287+
catch (ArgumentException)
288+
{
289+
Assert.Fail("Should not throw ArgumentException when explicit token is provided");
290+
}
291+
catch (Exception e)
292+
{
293+
Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
294+
}
295+
}
178296
}
179297
}
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Contentstack.Management.Core.Models;
4+
using Contentstack.Management.Core.Tests.Model;
5+
using Microsoft.VisualStudio.TestTools.UnitTesting;
6+
7+
namespace Contentstack.Management.Core.Tests.IntegrationTest
8+
{
9+
[TestClass]
10+
public class Contentstack016_VariantsTest
11+
{
12+
private Stack _stack;
13+
14+
[TestInitialize]
15+
public void Initialize()
16+
{
17+
StackResponse response = StackResponse.getStack(Contentstack.Client.serializer);
18+
_stack = Contentstack.Client.Stack(response.Stack.APIKey);
19+
}
20+
21+
[TestMethod]
22+
[DoNotParallelize]
23+
public async Task Test001_Should_Create_Variants()
24+
{
25+
try
26+
{
27+
VariantsModel variantsModel = new VariantsModel();
28+
ContentstackResponse response = await _stack.Variants().CreateAsync(variantsModel);
29+
Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode);
30+
}
31+
catch (Exception e)
32+
{
33+
Assert.Fail(e.Message);
34+
}
35+
}
36+
37+
[TestMethod]
38+
[DoNotParallelize]
39+
public async Task Test002_Should_Fetch_Variants_By_Uid()
40+
{
41+
try
42+
{
43+
// First create a variant to ensure we have something to fetch
44+
VariantsModel variantsModel = new VariantsModel();
45+
ContentstackResponse createResponse = await _stack.Variants().CreateAsync(variantsModel);
46+
Assert.AreEqual(System.Net.HttpStatusCode.Created, createResponse.StatusCode);
47+
48+
// Extract UID from created variant
49+
var createdVariant = createResponse.OpenJObjectResponse();
50+
string variantUid = createdVariant["variant"]["uid"].ToString();
51+
52+
// Test fetching by UID
53+
ContentstackResponse fetchResponse = await _stack.Variants(variantUid).FetchAsync();
54+
Assert.IsTrue(fetchResponse.StatusCode == System.Net.HttpStatusCode.OK ||
55+
fetchResponse.StatusCode == System.Net.HttpStatusCode.NotFound);
56+
}
57+
catch (Exception e)
58+
{
59+
Assert.Fail(e.Message);
60+
}
61+
}
62+
63+
[TestMethod]
64+
[DoNotParallelize]
65+
public async Task Test003_Should_FetchByUid_Multiple_Variants()
66+
{
67+
try
68+
{
69+
// Create multiple variants first
70+
VariantsModel variantsModel1 = new VariantsModel();
71+
VariantsModel variantsModel2 = new VariantsModel();
72+
73+
ContentstackResponse createResponse1 = await _stack.Variants().CreateAsync(variantsModel1);
74+
ContentstackResponse createResponse2 = await _stack.Variants().CreateAsync(variantsModel2);
75+
76+
Assert.AreEqual(System.Net.HttpStatusCode.Created, createResponse1.StatusCode);
77+
Assert.AreEqual(System.Net.HttpStatusCode.Created, createResponse2.StatusCode);
78+
79+
// Extract UIDs from created variants
80+
var createdVariant1 = createResponse1.OpenJObjectResponse();
81+
var createdVariant2 = createResponse2.OpenJObjectResponse();
82+
string variantUid1 = createdVariant1["variant"]["uid"].ToString();
83+
string variantUid2 = createdVariant2["variant"]["uid"].ToString();
84+
85+
// Test fetching multiple variants by UIDs
86+
string[] uids = { variantUid1, variantUid2 };
87+
ContentstackResponse fetchResponse = await _stack.Variants().FetchByUidAsync(uids);
88+
89+
Assert.IsTrue(fetchResponse.StatusCode == System.Net.HttpStatusCode.OK ||
90+
fetchResponse.StatusCode == System.Net.HttpStatusCode.NotFound);
91+
}
92+
catch (Exception e)
93+
{
94+
Assert.Fail(e.Message);
95+
}
96+
}
97+
98+
[TestMethod]
99+
[DoNotParallelize]
100+
public async Task Test004_Should_FetchByUid_Single_Variant()
101+
{
102+
try
103+
{
104+
// Create a variant first
105+
VariantsModel variantsModel = new VariantsModel();
106+
ContentstackResponse createResponse = await _stack.Variants().CreateAsync(variantsModel);
107+
Assert.AreEqual(System.Net.HttpStatusCode.Created, createResponse.StatusCode);
108+
109+
// Extract UID from created variant
110+
var createdVariant = createResponse.OpenJObjectResponse();
111+
string variantUid = createdVariant["variant"]["uid"].ToString();
112+
113+
// Test fetching single variant using FetchByUid
114+
string[] uids = { variantUid };
115+
ContentstackResponse fetchResponse = await _stack.Variants().FetchByUidAsync(uids);
116+
117+
Assert.IsTrue(fetchResponse.StatusCode == System.Net.HttpStatusCode.OK ||
118+
fetchResponse.StatusCode == System.Net.HttpStatusCode.NotFound);
119+
}
120+
catch (Exception e)
121+
{
122+
Assert.Fail(e.Message);
123+
}
124+
}
125+
126+
[TestMethod]
127+
[DoNotParallelize]
128+
public async Task Test005_Should_Handle_FetchByUid_With_Nonexistent_Uids()
129+
{
130+
try
131+
{
132+
// Test fetching with non-existent UIDs
133+
string[] nonExistentUids = { "nonexistent_uid_1", "nonexistent_uid_2" };
134+
ContentstackResponse fetchResponse = await _stack.Variants().FetchByUidAsync(nonExistentUids);
135+
136+
// Should return 404 or empty result, not crash
137+
Assert.IsTrue(fetchResponse.StatusCode == System.Net.HttpStatusCode.NotFound ||
138+
fetchResponse.StatusCode == System.Net.HttpStatusCode.OK);
139+
}
140+
catch (Exception e)
141+
{
142+
Assert.Fail(e.Message);
143+
}
144+
}
145+
146+
[TestMethod]
147+
[DoNotParallelize]
148+
public async Task Test006_Should_FetchByUid_Sync_Method()
149+
{
150+
try
151+
{
152+
// Create a variant first
153+
VariantsModel variantsModel = new VariantsModel();
154+
ContentstackResponse createResponse = _stack.Variants().Create(variantsModel);
155+
Assert.AreEqual(System.Net.HttpStatusCode.Created, createResponse.StatusCode);
156+
157+
// Extract UID from created variant
158+
var createdVariant = createResponse.OpenJObjectResponse();
159+
string variantUid = createdVariant["variant"]["uid"].ToString();
160+
161+
// Test synchronous FetchByUid
162+
string[] uids = { variantUid };
163+
ContentstackResponse fetchResponse = _stack.Variants().FetchByUid(uids);
164+
165+
Assert.IsTrue(fetchResponse.StatusCode == System.Net.HttpStatusCode.OK ||
166+
fetchResponse.StatusCode == System.Net.HttpStatusCode.NotFound);
167+
}
168+
catch (Exception e)
169+
{
170+
Assert.Fail(e.Message);
171+
}
172+
}
173+
174+
[TestMethod]
175+
[DoNotParallelize]
176+
public async Task Test007_Should_Delete_Variant()
177+
{
178+
try
179+
{
180+
// Create a variant first
181+
VariantsModel variantsModel = new VariantsModel();
182+
ContentstackResponse createResponse = await _stack.Variants().CreateAsync(variantsModel);
183+
Assert.AreEqual(System.Net.HttpStatusCode.Created, createResponse.StatusCode);
184+
185+
// Extract UID from created variant
186+
var createdVariant = createResponse.OpenJObjectResponse();
187+
string variantUid = createdVariant["variant"]["uid"].ToString();
188+
189+
// Test deleting the variant
190+
ContentstackResponse deleteResponse = await _stack.Variants(variantUid).DeleteAsync();
191+
Assert.IsTrue(deleteResponse.StatusCode == System.Net.HttpStatusCode.OK ||
192+
deleteResponse.StatusCode == System.Net.HttpStatusCode.NoContent ||
193+
deleteResponse.StatusCode == System.Net.HttpStatusCode.NotFound);
194+
}
195+
catch (Exception e)
196+
{
197+
Assert.Fail(e.Message);
198+
}
199+
}
200+
201+
[TestMethod]
202+
[DoNotParallelize]
203+
public void Test008_Should_Validate_FetchByUid_Parameters()
204+
{
205+
try
206+
{
207+
// Test with null UIDs
208+
Assert.ThrowsException<ArgumentException>(() => _stack.Variants().FetchByUid(null));
209+
210+
// Test with empty UIDs array
211+
string[] emptyUids = new string[0];
212+
Assert.ThrowsException<ArgumentException>(() => _stack.Variants().FetchByUid(emptyUids));
213+
}
214+
catch (Exception e)
215+
{
216+
Assert.Fail(e.Message);
217+
}
218+
}
219+
220+
[TestMethod]
221+
[DoNotParallelize]
222+
public async Task Test009_Should_Validate_FetchByUidAsync_Parameters()
223+
{
224+
try
225+
{
226+
// Test with null UIDs
227+
await Assert.ThrowsExceptionAsync<ArgumentException>(() => _stack.Variants().FetchByUidAsync(null));
228+
229+
// Test with empty UIDs array
230+
string[] emptyUids = new string[0];
231+
await Assert.ThrowsExceptionAsync<ArgumentException>(() => _stack.Variants().FetchByUidAsync(emptyUids));
232+
}
233+
catch (Exception e)
234+
{
235+
Assert.Fail(e.Message);
236+
}
237+
}
238+
239+
[TestMethod]
240+
[DoNotParallelize]
241+
public void Test010_Should_Validate_Instance_With_Uid_Cannot_Use_FetchByUid()
242+
{
243+
try
244+
{
245+
// Test that an instance with UID cannot call FetchByUid
246+
string instanceUid = "some_uid";
247+
string[] uids = { "uid1", "uid2" };
248+
249+
Assert.ThrowsException<InvalidOperationException>(() =>
250+
_stack.Variants(instanceUid).FetchByUid(uids));
251+
}
252+
catch (Exception e)
253+
{
254+
Assert.Fail(e.Message);
255+
}
256+
}
257+
}
258+
}

0 commit comments

Comments
 (0)