Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Membership/fb - Add Membership functionality (live site users) to the repository #88

Open
wants to merge 69 commits into
base: finished
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
02a8f9a
GH-86 :: Create registration widget directory
kentico-matthews Nov 7, 2024
f3bdd3f
GH-86 :: begin progress implementing registration
kentico-matthews Nov 7, 2024
4a6d75c
GH-86 :: fix spelling in widget properties
kentico-matthews Nov 7, 2024
8f10415
GH-86 :: progress
kentico-matthews Nov 8, 2024
cc13708
GH-86 :: registration widget progress
kentico-matthews Nov 8, 2024
a65c2e7
GH-89 :: add membership service - menat to handle all user manager stuff
dominikag2 Nov 8, 2024
ce2b417
GH-89 :: sign in widget WIP
dominikag2 Nov 12, 2024
ac553aa
GH-86 :: updates to registration server-side form validation
kentico-matthews Nov 12, 2024
b762bf4
Merge branch 'Membership/FB' of https://github.com/Kentico/xperience-…
kentico-matthews Nov 12, 2024
3e80516
GH-86 :: move UserManager code for registration into the MambershipSe…
kentico-matthews Nov 12, 2024
8c849ea
GH-86 :: fix label in registration form
kentico-matthews Nov 12, 2024
092a426
GH-86 :: add title to registration form and fix typo in custom Member…
kentico-matthews Nov 12, 2024
a4f86b6
GH-89 :: sign in widget WIP, relocate Authentication and registration…
dominikag2 Nov 12, 2024
b70af57
GH-86 :: add link/sign out widget, move registration controller, add …
kentico-matthews Nov 12, 2024
dea7c4d
Merge branch 'Membership/FB' of https://github.com/Kentico/xperience-…
kentico-matthews Nov 12, 2024
86c1ef6
GH-89 :: extract sign in logic into the Membership service
dominikag2 Nov 12, 2024
0074b13
GH-86 :: add sign out functionality
kentico-matthews Nov 12, 2024
0519b6d
GH-86 :: add widget configuration validation
kentico-matthews Nov 12, 2024
21869a8
GH-86 :: add registration page, enable non-required strings in form, …
kentico-matthews Nov 12, 2024
b53ef0c
GH-89 :: sign in form signs you in, but errors nor the redirect after…
dominikag2 Nov 13, 2024
53548cb
GH-89 :: fix error rendering for sing-in
dominikag2 Nov 13, 2024
cc962e4
GH-89 :: fix sign in button formatting in Sign in widget; Add LinkOrS…
dominikag2 Nov 13, 2024
cfd2bd6
GH-89 :: UI improvements
dominikag2 Nov 13, 2024
e439654
GH-86 :: adjust form target and controller action name for sign out, …
kentico-matthews Nov 13, 2024
8709d61
GH-89 :: add contact mapping for membership, update custom field name…
kentico-matthews Nov 13, 2024
b173135
GH-89 :: redirecting after successful sign in.
dominikag2 Nov 13, 2024
72bd1b2
GH-89 :: redirect page after sign in - let user pick is in the widget…
dominikag2 Nov 14, 2024
f05d5a3
GH-89 :: set code to remove cookies when the member signs out, so the…
kentico-matthews Nov 14, 2024
bf20129
Merge branch 'Membership/FB' of https://github.com/Kentico/xperience-…
kentico-matthews Nov 14, 2024
512b35b
GH-89 :: Navigate to sign in page from button in the header if not si…
dominikag2 Nov 14, 2024
a2f8a86
GH-89 :: move mapper to services, add service to map members to conta…
kentico-matthews Nov 14, 2024
9bda0cd
Merge branch 'Membership/FB' of https://github.com/Kentico/xperience-…
kentico-matthews Nov 14, 2024
6c1aea9
GH-89 :: Handle login success with JS and return partial view on error
dominikag2 Nov 14, 2024
b005ed9
GH-86 :: add translations and point registration page link to sign in…
kentico-matthews Nov 14, 2024
b2e337a
Merge branch 'Membership/FB' of https://github.com/Kentico/xperience-…
kentico-matthews Nov 14, 2024
1719ca2
GH-89 :: fix the order of middlewares, wrap authentication js in VC
dominikag2 Nov 14, 2024
52d5929
GH-89 :: remove unused parameter and nuget package. make the Sign in …
dominikag2 Nov 15, 2024
e931ee1
GH-86 :: refactoring
kentico-matthews Nov 15, 2024
ff0a48a
Merge branch 'Membership/FB' of https://github.com/Kentico/xperience-…
kentico-matthews Nov 15, 2024
ddf7a96
GH-89 :: Sign up and Sign in - add some styling
dominikag2 Nov 15, 2024
0e3b55a
GH-86 :: tests for log out widget
kentico-matthews Nov 18, 2024
a3d410e
GH-91 :: Implement email confirmation after registration
dominikag2 Nov 19, 2024
2f85e44
GH-90 :: Redirect unauthenticated requests for secured pages to sign …
kentico-matthews Nov 19, 2024
c0d78b1
GH-89 :: sign in redirect - WIP
dominikag2 Nov 19, 2024
297606f
GH-89 :: remove authentication scripts - not needed anymore, we chose…
dominikag2 Nov 19, 2024
a2656f2
GH-90 :: Add functionality to return member to the secured page they …
kentico-matthews Nov 19, 2024
1846652
Merge branch 'Membership/FB' of https://github.com/Kentico/xperience-…
kentico-matthews Nov 19, 2024
339ca06
GH-90 :: add summary for new service method
kentico-matthews Nov 19, 2024
dd42d36
GH-87 :: add membership service documentation
dominikag2 Nov 19, 2024
f3660ab
Merge branch 'Membership/FB' of https://github.com/Kentico/xperience-…
kentico-matthews Nov 19, 2024
d0738fa
GH-86 :: Fix localization in registration action
kentico-matthews Nov 20, 2024
e8fe880
GH-90 :: Add language checks when redirecting 403 to sign in page
kentico-matthews Nov 20, 2024
351603b
GH-89 :: Merge contacts when signing in
kentico-matthews Nov 20, 2024
30f93fb
GH-90 :: Add functionality to hide or prompt login in listing page fo…
kentico-matthews Nov 21, 2024
ee349d6
GH-90 :: adjust widget configuration on widget samples page to includ…
kentico-matthews Nov 21, 2024
e46ea8d
GH-69 :: fix missing page urls for spanish in articles
kentico-matthews Nov 21, 2024
bb55829
GH-69 :: Fix spanish URL paths for CI copied articles
kentico-matthews Nov 21, 2024
8d36270
GH-90 :: Add secured checks to content querying, adjust url retrieval…
kentico-matthews Nov 21, 2024
a4c4e50
GH-90 :: add translations, small refactor on sign in prompt
kentico-matthews Nov 21, 2024
53e3e56
GH-92 :: initial code for reset password functionality,
kentico-matthews Nov 25, 2024
9a71075
GH-92 :: improvements to localization of reset password functionality
kentico-matthews Nov 26, 2024
31d6a0f
GH-93 :: initial progress on profile page
kentico-matthews Nov 26, 2024
1a609ce
no message
dominikag2 Nov 27, 2024
dcfbd38
GH-87 :: implement review suggestions (together with the previous com…
dominikag2 Nov 27, 2024
91f4e23
GH-91 :: implement review suggestions - fix multilingual URLs
dominikag2 Nov 27, 2024
f799c82
GH-93 :: Continue progress on profile page
kentico-matthews Nov 27, 2024
61f2fb7
GH-93 :: register UpdateProfileService with DI
kentico-matthews Nov 27, 2024
1cb5ea9
Merge branch 'Membership/FB' of https://github.com/Kentico/xperience-…
kentico-matthews Nov 27, 2024
b6859ad
GH-93 :: add localization, rearrange pages, small refactors
kentico-matthews Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// using CMS.ContactManagement;
// using CMS.DataEngine;
// using Kentico.Web.Mvc;
// using Moq;
// using TrainingGuides.Web.Features.Membership;
// using TrainingGuides.Web.Features.Membership.Services;
// using Xunit;

namespace TrainingGuides.Web.Tests.Features.Membership.Services;

public class MemberContactServiceTests
{
// Commented out pending investigation into AbstractInfoBase and how to test something that uses its methods.

// private readonly Mock<IInfoProvider<ContactInfo>> contactInfoProviderMock;
// private readonly Mock<ICookieAccessor> cookieAccessorMock;
// private readonly Mock<ICurrentContactProvider> currentContactProviderMock;

// private const string GIVEN_NAME_1 = "John";
// private const string GIVEN_NAME_2 = "NotJohn";
// private const string FAMILY_NAME_1 = "Doe";
// private const string FAMILY_NAME_2 = "NotDoe";
// private const string EMAIL_1 = "JohnDoe@localhost.local";
// private const string EMAIL_2 = "NotJohnDoe@localhost.local";

// public MemberContactServiceTests()
// {
// contactInfoProviderMock = new Mock<IInfoProvider<ContactInfo>>();
// cookieAccessorMock = new Mock<ICookieAccessor>();
// currentContactProviderMock = new Mock<ICurrentContactProvider>();
// }

// private ContactInfo BuildSampleContactInfo(string firstName, string lastName, string email) =>
// new()
// {
// ContactFirstName = firstName,
// ContactLastName = lastName,
// ContactEmail = email,
// ContactID = 1,
// ContactGUID = Guid.NewGuid()
// };

// private GuidesMember BuildSampleGuidesMember(string givenName, string familyName, string email) =>
// new()
// {
// GivenName = givenName,
// FamilyName = familyName,
// Email = email,
// Id = 1,
// };

// [Fact]
// public void TransferMemberFieldsToContact_Does_Not_Overwrite_Email()
// {
// // Arrange
// var memberContactService = new MemberContactService(
// contactInfoProvider: contactInfoProviderMock.Object,
// cookieAccessor: cookieAccessorMock.Object,
// currentContactProvider: currentContactProviderMock.Object);

// var guidesMember = BuildSampleGuidesMember(
// givenName: GIVEN_NAME_1,
// familyName: FAMILY_NAME_1,
// email: EMAIL_1);

// var contact = BuildSampleContactInfo(
// firstName: GIVEN_NAME_1,
// lastName: FAMILY_NAME_1,
// email: EMAIL_2);

// var newContact = memberContactService.TransferMemberFieldsToContact(guidesMember, contact);

// Assert.NotEqual(guidesMember.Email, newContact.ContactEmail);
// }

// [Fact]
// public void TransferMemberFieldsToContact_Overwrites_Blank_Email()
// {
// // Arrange
// var memberContactService = new MemberContactService(
// contactInfoProvider: contactInfoProviderMock.Object,
// cookieAccessor: cookieAccessorMock.Object,
// currentContactProvider: currentContactProviderMock.Object);

// var guidesMember = BuildSampleGuidesMember(
// givenName: GIVEN_NAME_1,
// familyName: FAMILY_NAME_1,
// email: EMAIL_1);

// var contact = BuildSampleContactInfo(
// firstName: GIVEN_NAME_1,
// lastName: FAMILY_NAME_1,
// email: string.Empty);

// var newContact = memberContactService.TransferMemberFieldsToContact(guidesMember, contact);

// Assert.Equal(guidesMember.Email, newContact.ContactEmail);
// }

// [Fact]
// public void TransferMemberFieldsToContact_Overwrites_FirstName()
// {
// // Arrange
// var memberContactService = new MemberContactService(
// contactInfoProvider: contactInfoProviderMock.Object,
// cookieAccessor: cookieAccessorMock.Object,
// currentContactProvider: currentContactProviderMock.Object);

// var guidesMember = BuildSampleGuidesMember(
// givenName: GIVEN_NAME_1,
// familyName: FAMILY_NAME_1,
// email: EMAIL_1);

// var contact = BuildSampleContactInfo(
// firstName: GIVEN_NAME_2,
// lastName: FAMILY_NAME_2,
// email: string.Empty);

// var newContact = memberContactService.TransferMemberFieldsToContact(guidesMember, contact);

// Assert.Equal(guidesMember.GivenName, newContact.ContactFirstName);
// }

// [Fact]
// public void TransferMemberFieldsToContact_Overwrites_LastName()
// {
// // Arrange
// var memberContactService = new MemberContactService(
// contactInfoProvider: contactInfoProviderMock.Object,
// cookieAccessor: cookieAccessorMock.Object,
// currentContactProvider: currentContactProviderMock.Object);

// var guidesMember = BuildSampleGuidesMember(
// givenName: GIVEN_NAME_1,
// familyName: FAMILY_NAME_1,
// email: EMAIL_1);

// var contact = BuildSampleContactInfo(
// firstName: GIVEN_NAME_2,
// lastName: FAMILY_NAME_2,
// email: string.Empty);

// var newContact = memberContactService.TransferMemberFieldsToContact(guidesMember, contact);

// Assert.Equal(guidesMember.FamilyName, newContact.ContactLastName);
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using CMS.Websites;
using Kentico.Content.Web.Mvc.Routing;
using Moq;
using TrainingGuides.Web.Features.Membership.Services;
using TrainingGuides.Web.Features.Membership.Widgets.LinkOrSignOut;
using TrainingGuides.Web.Features.Shared.Services;
using Xunit;

namespace TrainingGuides.Web.Tests.Features.Membership.Widgets.LinkOrSignOut;

public class LinkOrSignOutWidgetViewComponentTests
{
private readonly Mock<IWebPageUrlRetriever> webPageUrlRetrieverMock;
private readonly Mock<IPreferredLanguageRetriever> preferredLanguageRetrieverMock;
private readonly Mock<IMembershipService> membershipServiceMock;
private readonly Mock<IHttpRequestService> httpRequestServiceMock;

private const string BASE_URL = "http://localhost:5000";
private const string UNAUTHENTICATED_TEXT = "Already have an account?";
private const string UNAUTHENTICATED_BUTTON_TEXT = "Sign In";
private const string AUTHENTICATED_TEXT = "You're already signed in.";
private const string AUTHENTICATED_BUTTON_TEXT = "Sign Out";
private const string LANGUAGE = "en";
private const string PAGE_URL = "/page";
private const string CURRENT_PAGE_URL = "/current-page";
private static readonly Guid unauthenticatedGuid = new("00000000-0000-0000-0000-000000000000");

private readonly LinkOrSignOutWidgetProperties widgetProperties;
private readonly WebPageUrl webPageUrl;

public LinkOrSignOutWidgetViewComponentTests()
{
webPageUrlRetrieverMock = new Mock<IWebPageUrlRetriever>();
webPageUrlRetrieverMock.Setup(x => x.Retrieve(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Guid pageGuid, string language, bool useAbsoluteUrl, CancellationToken cancellationToken) =>
{
return new WebPageUrl(PAGE_URL, $"{BASE_URL}{PAGE_URL}");
});

preferredLanguageRetrieverMock = new Mock<IPreferredLanguageRetriever>();
preferredLanguageRetrieverMock.Setup(x => x.Get()).Returns(LANGUAGE);

membershipServiceMock = new Mock<IMembershipService>();
membershipServiceMock.Setup(x => x.IsMemberAuthenticated()).ReturnsAsync(true);

httpRequestServiceMock = new Mock<IHttpRequestService>();
httpRequestServiceMock.Setup(x => x.GetBaseUrl()).Returns(BASE_URL);
httpRequestServiceMock.Setup(x => x.GetCurrentPageUrlForLanguage(It.IsAny<string>())).ReturnsAsync(CURRENT_PAGE_URL);

var relatedItem = new WebPageRelatedItem()
{
WebPageGuid = unauthenticatedGuid,
};

widgetProperties = new LinkOrSignOutWidgetProperties()
{
UnauthenticatedText = UNAUTHENTICATED_TEXT,
UnauthenticatedButtonText = UNAUTHENTICATED_BUTTON_TEXT,
UnauthenticatedTargetContentPage = [relatedItem],
AuthenticatedText = AUTHENTICATED_TEXT,
AuthenticatedButtonText = AUTHENTICATED_BUTTON_TEXT
};
}

[Fact]
public async Task BuildWidgetViewModel_ReturnsUnauthenticatedViewModel_WhenUserIsNotAuthenticated()
{
membershipServiceMock.Setup(x => x.IsMemberAuthenticated()).ReturnsAsync(false);

var viewComponent = new LinkOrSignOutWidgetViewComponent(webPageUrlRetrieverMock.Object, preferredLanguageRetrieverMock.Object, membershipServiceMock.Object, httpRequestServiceMock.Object);

var viewModel = await viewComponent.BuildWidgetViewModel(widgetProperties);

Assert.False(viewModel.IsAuthenticated);
Assert.Equal(UNAUTHENTICATED_TEXT, viewModel.Text);
Assert.Equal(UNAUTHENTICATED_BUTTON_TEXT, viewModel.ButtonText);
Assert.Equal(PAGE_URL, viewModel.Url);
}

[Fact]
public async Task BuildWidgetViewModel_ReturnsAuthenticatedViewModel_WhenUserIsAuthenticated()
{
membershipServiceMock.Setup(x => x.IsMemberAuthenticated()).ReturnsAsync(true);

var viewComponent = new LinkOrSignOutWidgetViewComponent(webPageUrlRetrieverMock.Object, preferredLanguageRetrieverMock.Object, membershipServiceMock.Object, httpRequestServiceMock.Object);

var viewModel = await viewComponent.BuildWidgetViewModel(widgetProperties);

Assert.True(viewModel.IsAuthenticated);
Assert.Equal(AUTHENTICATED_TEXT, viewModel.Text);
Assert.Equal(AUTHENTICATED_BUTTON_TEXT, viewModel.ButtonText);
Assert.Equal(CURRENT_PAGE_URL, viewModel.Url);
}

[Fact]
public async Task BuildWidgetViewModel_FallsBackToBaseUrl_WhenAuthenticatedAndCurrentPageUrlNotFound()
{
membershipServiceMock.Setup(x => x.IsMemberAuthenticated()).ReturnsAsync(true);

httpRequestServiceMock.Setup(x => x.GetCurrentPageUrlForLanguage(It.IsAny<string>())).ReturnsAsync(string.Empty);

var viewComponent = new LinkOrSignOutWidgetViewComponent(webPageUrlRetrieverMock.Object, preferredLanguageRetrieverMock.Object, membershipServiceMock.Object, httpRequestServiceMock.Object);

var viewModel = await viewComponent.BuildWidgetViewModel(widgetProperties);

Assert.Equal(BASE_URL, viewModel.Url);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using TrainingGuides.Web.Features.Membership.Widgets.LinkOrSignOut;
using Xunit;

namespace TrainingGuides.Web.Tests.Features.Membership.Widgets.LinkOrSignOut;

public class LinkOrSignOutWidgetViewModelTests
{
private readonly LinkOrSignOutWidgetViewModel viewModel;

public LinkOrSignOutWidgetViewModelTests()
{
viewModel = new();
}

[Fact]
public void WhenModelInitialized_Text_IsEmpty() => Assert.Equal(string.Empty, viewModel.Text);

[Fact]
public void WhenModelInitialized_ButtonText_IsEmpty() => Assert.Equal(string.Empty, viewModel.ButtonText);

[Fact]
public void WhenModelInitialized_Url_IsEmpty() => Assert.Equal(string.Empty, viewModel.Url);

[Fact]
public void IsMisconfigured_WhenButtonTextAndUrlAreSet_ReturnsFalse()
{
var viewModelAllFieldsSet = new LinkOrSignOutWidgetViewModel
{
Text = "Text",
ButtonText = "Button",
Url = "https://www.example.com"
};

Assert.False(viewModelAllFieldsSet.IsMisconfigured);
}

[Fact]
public void IsMisconfigured_WhenButtonTextIsMissing_ReturnsTrue()
{
var viewModelAllFieldsSet = new LinkOrSignOutWidgetViewModel
{
Text = "Text",
Url = "https://www.example.com"
};

Assert.True(viewModelAllFieldsSet.IsMisconfigured);
}

[Fact]
public void IsMisconfigured_WhenButtonUrlIsMissing_ReturnsTrue()
{
var viewModelAllFieldsSet = new LinkOrSignOutWidgetViewModel
{
Text = "Text",
ButtonText = "Button",
};

Assert.True(viewModelAllFieldsSet.IsMisconfigured);
}
}
Loading