Skip to content

Commit b4da510

Browse files
authored
Merge pull request #142 from contentstack/enhc/DX-5433
feat(DX-5433): Retrieve auth token exclusively via Login API
2 parents dd292c4 + 7a3026b commit b4da510

15 files changed

+485
-120
lines changed

Contentstack.Management.Core.Tests/Contentstack.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,6 @@ namespace Contentstack.Management.Core.Tests
1616
{
1717
public class Contentstack
1818
{
19-
private static readonly Lazy<ContentstackClient>
20-
client =
21-
new Lazy<ContentstackClient>(() =>
22-
{
23-
ContentstackClientOptions options = Config.GetSection("Contentstack").Get<ContentstackClientOptions>();
24-
var handler = new LoggingHttpHandler();
25-
var httpClient = new HttpClient(handler);
26-
return new ContentstackClient(httpClient, options);
27-
});
28-
29-
3019
private static readonly Lazy<IConfigurationRoot>
3120
config =
3221
new Lazy<IConfigurationRoot>(() =>
@@ -46,13 +35,28 @@ private static readonly Lazy<IConfigurationRoot>
4635
return Config.GetSection("Contentstack:Organization").Get<OrganizationModel>();
4736
});
4837

49-
public static ContentstackClient Client { get { return client.Value; } }
5038
public static IConfigurationRoot Config{ get { return config.Value; } }
5139
public static NetworkCredential Credential { get { return credential.Value; } }
5240
public static OrganizationModel Organization { get { return organization.Value; } }
5341

5442
public static StackModel Stack { get; set; }
5543

44+
/// <summary>
45+
/// Creates a new ContentstackClient, logs in via the Login API (never from config),
46+
/// and returns the authenticated client. Callers are responsible for calling Logout()
47+
/// when done.
48+
/// </summary>
49+
public static ContentstackClient CreateAuthenticatedClient()
50+
{
51+
ContentstackClientOptions options = Config.GetSection("Contentstack").Get<ContentstackClientOptions>();
52+
options.Authtoken = null;
53+
var handler = new LoggingHttpHandler();
54+
var httpClient = new HttpClient(handler);
55+
var client = new ContentstackClient(httpClient, options);
56+
client.Login(Credential);
57+
return client;
58+
}
59+
5660
public static T serialize<T>(JsonSerializer serializer, string filePath)
5761
{
5862
string response = GetResourceText(filePath);

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

Lines changed: 207 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
using Contentstack.Management.Core.Models;
66
using Contentstack.Management.Core.Tests.Helpers;
77
using Microsoft.VisualStudio.TestTools.UnitTesting;
8-
using Microsoft.Extensions.Configuration;
9-
using Microsoft.Extensions.Options;
10-
using System.Threading;
118
using Contentstack.Management.Core.Queryable;
129
using Newtonsoft.Json.Linq;
1310

@@ -16,7 +13,6 @@ namespace Contentstack.Management.Core.Tests.IntegrationTest
1613
[TestClass]
1714
public class Contentstack001_LoginTest
1815
{
19-
private readonly IConfigurationRoot _configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
2016

2117
private static ContentstackClient CreateClientWithLogging()
2218
{
@@ -48,25 +44,24 @@ public void Test001_Should_Return_Failuer_On_Wrong_Login_Credentials()
4844

4945
[TestMethod]
5046
[DoNotParallelize]
51-
public void Test002_Should_Return_Failuer_On_Wrong_Async_Login_Credentials()
47+
public async System.Threading.Tasks.Task Test002_Should_Return_Failuer_On_Wrong_Async_Login_Credentials()
5248
{
5349
TestOutputLogger.LogContext("TestScenario", "WrongCredentialsAsync");
5450
ContentstackClient client = CreateClientWithLogging();
5551
NetworkCredential credentials = new NetworkCredential("mock_user", "mock_pasword");
56-
var response = client.LoginAsync(credentials);
57-
58-
response.ContinueWith((t) =>
59-
{
60-
if (t.IsCompleted && t.Status == System.Threading.Tasks.TaskStatus.Faulted)
61-
{
62-
ContentstackErrorException errorException = t.Exception.InnerException as ContentstackErrorException;
63-
AssertLogger.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode, "StatusCode");
64-
AssertLogger.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.Message, "Message");
65-
AssertLogger.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.ErrorMessage, "ErrorMessage");
66-
AssertLogger.AreEqual(104, errorException.ErrorCode, "ErrorCode");
67-
}
68-
});
69-
Thread.Sleep(3000);
52+
53+
try
54+
{
55+
await client.LoginAsync(credentials);
56+
AssertLogger.Fail("Expected exception for wrong credentials");
57+
}
58+
catch (ContentstackErrorException errorException)
59+
{
60+
AssertLogger.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode, "StatusCode");
61+
AssertLogger.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.Message, "Message");
62+
AssertLogger.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.ErrorMessage, "ErrorMessage");
63+
AssertLogger.AreEqual(104, errorException.ErrorCode, "ErrorCode");
64+
}
7065
}
7166

7267
[TestMethod]
@@ -304,5 +299,198 @@ public void Test011_Should_Prefer_Explicit_Token_Over_MfaSecret()
304299
AssertLogger.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
305300
}
306301
}
302+
303+
[TestMethod]
304+
[DoNotParallelize]
305+
public void Test012_Should_Throw_InvalidOperation_When_Already_LoggedIn_Sync()
306+
{
307+
TestOutputLogger.LogContext("TestScenario", "AlreadyLoggedInSync");
308+
ContentstackClient client = CreateClientWithLogging();
309+
310+
try
311+
{
312+
client.Login(Contentstack.Credential);
313+
AssertLogger.IsNotNull(client.contentstackOptions.Authtoken, "Authtoken");
314+
315+
AssertLogger.ThrowsException<InvalidOperationException>(() =>
316+
client.Login(Contentstack.Credential), "AlreadyLoggedIn");
317+
318+
client.Logout();
319+
}
320+
catch (Exception e)
321+
{
322+
AssertLogger.Fail($"Unexpected exception: {e.GetType().Name} - {e.Message}");
323+
}
324+
}
325+
326+
[TestMethod]
327+
[DoNotParallelize]
328+
public async System.Threading.Tasks.Task Test013_Should_Throw_InvalidOperation_When_Already_LoggedIn_Async()
329+
{
330+
TestOutputLogger.LogContext("TestScenario", "AlreadyLoggedInAsync");
331+
ContentstackClient client = CreateClientWithLogging();
332+
333+
try
334+
{
335+
await client.LoginAsync(Contentstack.Credential);
336+
AssertLogger.IsNotNull(client.contentstackOptions.Authtoken, "Authtoken");
337+
338+
await System.Threading.Tasks.Task.Run(() =>
339+
AssertLogger.ThrowsException<InvalidOperationException>(() =>
340+
client.LoginAsync(Contentstack.Credential).GetAwaiter().GetResult(), "AlreadyLoggedInAsync"));
341+
342+
await client.LogoutAsync();
343+
}
344+
catch (Exception e)
345+
{
346+
AssertLogger.Fail($"Unexpected exception: {e.GetType().Name} - {e.Message}");
347+
}
348+
}
349+
350+
[TestMethod]
351+
[DoNotParallelize]
352+
public void Test014_Should_Throw_ArgumentNullException_For_Null_Credentials_Sync()
353+
{
354+
TestOutputLogger.LogContext("TestScenario", "NullCredentialsSync");
355+
ContentstackClient client = CreateClientWithLogging();
356+
357+
AssertLogger.ThrowsException<ArgumentNullException>(() =>
358+
client.Login(null), "NullCredentials");
359+
}
360+
361+
[TestMethod]
362+
[DoNotParallelize]
363+
public void Test015_Should_Throw_ArgumentNullException_For_Null_Credentials_Async()
364+
{
365+
TestOutputLogger.LogContext("TestScenario", "NullCredentialsAsync");
366+
ContentstackClient client = CreateClientWithLogging();
367+
368+
AssertLogger.ThrowsException<ArgumentNullException>(() =>
369+
client.LoginAsync(null).GetAwaiter().GetResult(), "NullCredentialsAsync");
370+
}
371+
372+
[TestMethod]
373+
[DoNotParallelize]
374+
public async System.Threading.Tasks.Task Test016_Should_Throw_ArgumentException_For_Invalid_MfaSecret_Async()
375+
{
376+
TestOutputLogger.LogContext("TestScenario", "InvalidMfaSecretAsync");
377+
ContentstackClient client = CreateClientWithLogging();
378+
NetworkCredential credentials = new NetworkCredential("test_user", "test_password");
379+
string invalidMfaSecret = "INVALID_BASE32_SECRET!@#";
380+
381+
try
382+
{
383+
await client.LoginAsync(credentials, null, invalidMfaSecret);
384+
AssertLogger.Fail("Expected ArgumentException for invalid MFA secret");
385+
}
386+
catch (ArgumentException)
387+
{
388+
AssertLogger.IsTrue(true, "ArgumentException thrown as expected for async");
389+
}
390+
catch (Exception e)
391+
{
392+
AssertLogger.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
393+
}
394+
}
395+
396+
[TestMethod]
397+
[DoNotParallelize]
398+
public void Test017_Should_Handle_Valid_Credentials_With_TfaToken_Sync()
399+
{
400+
TestOutputLogger.LogContext("TestScenario", "WrongTfaTokenSync");
401+
ContentstackClient client = CreateClientWithLogging();
402+
403+
try
404+
{
405+
client.Login(Contentstack.Credential, "000000");
406+
// Account does not have 2FA enabled — tfa_token is ignored by the API and login succeeds.
407+
// This is a valid outcome; assert token is set and clean up.
408+
AssertLogger.IsNotNull(client.contentstackOptions.Authtoken, "Authtoken");
409+
client.Logout();
410+
}
411+
catch (ContentstackErrorException errorException)
412+
{
413+
// Account has 2FA enabled — wrong token is correctly rejected with 422.
414+
AssertLogger.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode, "StatusCode");
415+
AssertLogger.IsTrue(errorException.ErrorCode > 0, "TfaErrorCode");
416+
}
417+
catch (Exception e)
418+
{
419+
AssertLogger.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
420+
}
421+
}
422+
423+
[TestMethod]
424+
[DoNotParallelize]
425+
public async System.Threading.Tasks.Task Test018_Should_Handle_Valid_Credentials_With_TfaToken_Async()
426+
{
427+
TestOutputLogger.LogContext("TestScenario", "WrongTfaTokenAsync");
428+
ContentstackClient client = CreateClientWithLogging();
429+
430+
try
431+
{
432+
await client.LoginAsync(Contentstack.Credential, "000000");
433+
// Account does not have 2FA enabled — tfa_token is ignored by the API and login succeeds.
434+
// This is a valid outcome; assert token is set and clean up.
435+
AssertLogger.IsNotNull(client.contentstackOptions.Authtoken, "Authtoken");
436+
await client.LogoutAsync();
437+
}
438+
catch (ContentstackErrorException errorException)
439+
{
440+
// Account has 2FA enabled — wrong token is correctly rejected with 422.
441+
AssertLogger.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode, "StatusCode");
442+
AssertLogger.IsTrue(errorException.ErrorCode > 0, "TfaErrorCodeAsync");
443+
}
444+
catch (Exception e)
445+
{
446+
AssertLogger.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
447+
}
448+
}
449+
450+
[TestMethod]
451+
[DoNotParallelize]
452+
public void Test019_Should_Not_Include_TfaToken_When_MfaSecret_Is_Empty_Sync()
453+
{
454+
TestOutputLogger.LogContext("TestScenario", "EmptyMfaSecretSync");
455+
ContentstackClient client = CreateClientWithLogging();
456+
NetworkCredential credentials = new NetworkCredential("mock_user", "mock_password");
457+
458+
try
459+
{
460+
client.Login(credentials, null, "");
461+
}
462+
catch (ContentstackErrorException errorException)
463+
{
464+
AssertLogger.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode, "StatusCode");
465+
AssertLogger.AreEqual(104, errorException.ErrorCode, "ErrorCode");
466+
}
467+
catch (Exception e)
468+
{
469+
AssertLogger.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
470+
}
471+
}
472+
473+
[TestMethod]
474+
[DoNotParallelize]
475+
public async System.Threading.Tasks.Task Test020_Should_Not_Include_TfaToken_When_MfaSecret_Is_Null_Async()
476+
{
477+
TestOutputLogger.LogContext("TestScenario", "NullMfaSecretAsync");
478+
ContentstackClient client = CreateClientWithLogging();
479+
NetworkCredential credentials = new NetworkCredential("mock_user", "mock_password");
480+
481+
try
482+
{
483+
await client.LoginAsync(credentials, null, null);
484+
}
485+
catch (ContentstackErrorException errorException)
486+
{
487+
AssertLogger.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode, "StatusCode");
488+
AssertLogger.AreEqual(104, errorException.ErrorCode, "ErrorCode");
489+
}
490+
catch (Exception e)
491+
{
492+
AssertLogger.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}");
493+
}
494+
}
307495
}
308496
}

0 commit comments

Comments
 (0)