Skip to content

Commit

Permalink
#22 #15 favorite and get article (endpoints go hand-in-hand)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgroves committed Sep 1, 2023
1 parent de2fa99 commit dba7543
Show file tree
Hide file tree
Showing 31 changed files with 702 additions and 42 deletions.
27 changes: 27 additions & 0 deletions Conduit/Conduit.Migrations/006_CreateCollectionForFavorites.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NoSqlMigrator.Infrastructure;

namespace Conduit.Migrations;

// Manual alternative: create a Favorites collection in _default scope
[Migration(6)]
public class CreateCollectionForFavorites : MigrateBase
{
private readonly string? _scopeName;

public CreateCollectionForFavorites()
{
_scopeName = _config["Couchbase:ScopeName"];
}

public override void Up()
{
Create.Collection("Favorites")
.InScope(_scopeName);
}

public override void Down()
{
Delete.Collection("Favorites")
.FromScope(_scopeName);
}
}
1 change: 1 addition & 0 deletions Conduit/Conduit.Tests/Conduit.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<ItemGroup>
<Folder Include="Integration\Articles\Handlers\" />
<Folder Include="Extensions\" />
<Folder Include="Integration\Articles\Services\ArticlesDataService\" />
<Folder Include="Integration\Users\Controllers\UserController\" />
<Folder Include="Integration\Users\Services\UserDataServiceTests\" />
<Folder Include="TestHelpers\Dto\" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class CreateArticleHandlerTests : CouchbaseIntegrationTest
private TagsDataService _tagsDataService;
private IConduitArticlesCollectionProvider _articleCollectionProvider;
private ArticlesDataService _articleDataService;
private IConduitFavoritesCollectionProvider _favoriteCollectionProvider;

public override async Task Setup()
{
Expand All @@ -31,15 +32,19 @@ public override async Task Setup()
b
.AddScope("_default")
.AddCollection<IConduitArticlesCollectionProvider>("Articles");
b
.AddScope("_default")
.AddCollection<IConduitFavoritesCollectionProvider>("Favorites");
});

_tagsCollectionProvider = ServiceProvider.GetRequiredService<IConduitTagsCollectionProvider>();
_articleCollectionProvider = ServiceProvider.GetRequiredService<IConduitArticlesCollectionProvider>();
_favoriteCollectionProvider = ServiceProvider.GetRequiredService<IConduitFavoritesCollectionProvider>();

// setup handler and dependencies
_tagsDataService = new TagsDataService(_tagsCollectionProvider);
var validator = new CreateArticleRequestValidator(_tagsDataService);
_articleDataService = new ArticlesDataService(_articleCollectionProvider);
_articleDataService = new ArticlesDataService(_articleCollectionProvider, _favoriteCollectionProvider);
var slugService = new SlugService(new SlugHelper());
_handler = new CreateArticleHandler(validator, _articleDataService, slugService);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Conduit.Tests.TestHelpers.Data;
using Conduit.Web.Articles.Handlers;
using Conduit.Web.Articles.Services;
using Conduit.Web.DataAccess.Providers;
using Couchbase.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

namespace Conduit.Tests.Integration.Articles.Handlers;

public class FavoriteArticleHandlerTests : CouchbaseIntegrationTest
{
private IConduitArticlesCollectionProvider _articleCollectionProvider;
private FavoriteArticleHandler _handler;
private IConduitFavoritesCollectionProvider _favoritesCollectionProvider;
private ArticlesDataService _articleDataService;
private IConduitUsersCollectionProvider _usersCollectionProvider;

public override async Task Setup()
{
await base.Setup();

ServiceCollection.AddCouchbaseBucket<IConduitBucketProvider>("ConduitIntegrationTests", b =>
{
b
.AddScope("_default")
.AddCollection<IConduitArticlesCollectionProvider>("Articles");
b
.AddScope("_default")
.AddCollection<IConduitFavoritesCollectionProvider>("Favorites");
b
.AddScope("_default")
.AddCollection<IConduitUsersCollectionProvider>("Users");
});

_articleCollectionProvider = ServiceProvider.GetRequiredService<IConduitArticlesCollectionProvider>();
_favoritesCollectionProvider = ServiceProvider.GetRequiredService<IConduitFavoritesCollectionProvider>();
_usersCollectionProvider = ServiceProvider.GetRequiredService<IConduitUsersCollectionProvider>();
_articleDataService = new ArticlesDataService(_articleCollectionProvider, _favoritesCollectionProvider);

// setup handler and dependencies
_handler = new FavoriteArticleHandler(_articleDataService);
}

[Test]
public async Task FavoriteArticleHandler_adds_article_to_a_users_favorites()
{
// arrange
var user = await _usersCollectionProvider.CreateUserInDatabase();
var article = await _articleCollectionProvider.CreateArticleInDatabase();
var request = new FavoriteArticleRequest();
request.Slug = article.Slug;
request.Username = user.Username;

// act
var result = await _handler.Handle(request, CancellationToken.None);

// assert
Assert.That(result.ValidationErrors == null || !result.ValidationErrors.Any(), Is.True);
await _favoritesCollectionProvider.AssertExists(user.Username, x =>
{
Assert.That(x.Contains(request.Slug), Is.True);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Conduit.Tests.TestHelpers;
using Conduit.Tests.TestHelpers.Data;
using Conduit.Web.Articles.Handlers;
using Conduit.Web.Articles.Services;
using Conduit.Web.DataAccess.Providers;
using Conduit.Web.Extensions;
using Conduit.Web.Follows.Services;
using Conduit.Web.Users.Services;
using Couchbase.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Conduit.Tests.Integration.Articles.Handlers;

public class GetArticleHandlerTests : CouchbaseIntegrationTest
{
private IConduitArticlesCollectionProvider _articleCollectionProvider;
private IConduitUsersCollectionProvider _usersCollectionProvider;
private IConduitFollowsCollectionProvider _followsCollectionProvider;
private ArticlesDataService _articleDataService;
private IConduitFavoritesCollectionProvider _favoriteCollectionProvider;
private GetArticleHandler _handler;
private AuthService _authService;
private UserDataService _userDataService;
private FollowsDataService _followDataService;
private Random _random;

public override async Task Setup()
{
await base.Setup();

ServiceCollection.AddCouchbaseBucket<IConduitBucketProvider>("ConduitIntegrationTests", b =>
{
b
.AddScope("_default")
.AddCollection<IConduitUsersCollectionProvider>("Users");
b
.AddScope("_default")
.AddCollection<IConduitArticlesCollectionProvider>("Articles");
b
.AddScope("_default")
.AddCollection<IConduitFollowsCollectionProvider>("Follows");
b
.AddScope("_default")
.AddCollection<IConduitFavoritesCollectionProvider>("Favorites");
});

_usersCollectionProvider = ServiceProvider.GetRequiredService<IConduitUsersCollectionProvider>();
_articleCollectionProvider = ServiceProvider.GetRequiredService<IConduitArticlesCollectionProvider>();
_followsCollectionProvider = ServiceProvider.GetRequiredService<IConduitFollowsCollectionProvider>();
_favoriteCollectionProvider = ServiceProvider.GetRequiredService<IConduitFavoritesCollectionProvider>();

// setup handler and dependencies
var jwtSecrets = new JwtSecrets
{
Audience = "dummy-audience",
Issuer = "dummy-issuer",
SecurityKey = "dummy-securitykey"
};
_authService = new AuthService(new OptionsWrapper<JwtSecrets>(jwtSecrets));
_articleDataService = new ArticlesDataService(_articleCollectionProvider, _favoriteCollectionProvider);
_followDataService = new FollowsDataService(_followsCollectionProvider, _authService);
_userDataService = new UserDataService(_usersCollectionProvider, _authService);
_handler = new GetArticleHandler(_articleDataService, _userDataService, _followDataService);
_random = new Random();
}

[Test]
public async Task GetArticleHandler_Returns_article()
{
// arrange
var currentUser = await _usersCollectionProvider.CreateUserInDatabase();
var authorUser = await _usersCollectionProvider.CreateUserInDatabase();
var article = await _articleCollectionProvider.CreateArticleInDatabase(authorUsername: authorUser.Username);
var request = new GetArticleRequest(article.Slug, currentUser.Username);

// act
var result = await _handler.Handle(request, CancellationToken.None);

// assert
Assert.That(result.ArticleView.Slug, Is.EqualTo(article.Slug));
Assert.That(result.ArticleView.Title, Is.EqualTo(article.Title));
Assert.That(result.ArticleView.Author.Username, Is.EqualTo(authorUser.Username));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Conduit.Tests.TestHelpers.Data;
using Conduit.Web.DataAccess.Providers;
using Couchbase.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

namespace Conduit.Tests.Integration.Articles.Services.ArticlesDataService;

[TestFixture]
public class FavoriteTests : CouchbaseIntegrationTest
{
private IConduitArticlesCollectionProvider _articleCollectionProvider;
private IConduitFavoritesCollectionProvider _favoriteCollectionProvider;
private Web.Articles.Services.ArticlesDataService _articleDataService;

[SetUp]
public override async Task Setup()
{
await base.Setup();

ServiceCollection.AddCouchbaseBucket<IConduitBucketProvider>("ConduitIntegrationTests", b =>
{
b
.AddScope("_default")
.AddCollection<IConduitArticlesCollectionProvider>("Articles");
b
.AddScope("_default")
.AddCollection<IConduitFavoritesCollectionProvider>("Favorites");
});

_articleCollectionProvider = ServiceProvider.GetRequiredService<IConduitArticlesCollectionProvider>();
_favoriteCollectionProvider = ServiceProvider.GetRequiredService<IConduitFavoritesCollectionProvider>();

_articleDataService = new Web.Articles.Services.ArticlesDataService(
_articleCollectionProvider,
_favoriteCollectionProvider);
}

[TestCase(0,1)]
[TestCase(73,74)]
public async Task Favoriting_works_and_increases_count(int initialCount, int expectedCount)
{
// arrange
var article = await _articleCollectionProvider.CreateArticleInDatabase(favoritesCount: initialCount);
var user = UserHelper.CreateUser();

// act
await _articleDataService.Favorite(article.Slug, user.Username);

// assert
await _favoriteCollectionProvider.AssertExists(user.Username, x =>
{
Assert.That(x.Contains(article.Slug), Is.True);
});
await _articleCollectionProvider.AssertExists(article.Slug, x =>
{
Assert.That(x.FavoritesCount, Is.EqualTo(expectedCount));
});
}
}
2 changes: 2 additions & 0 deletions Conduit/Conduit.Tests/Integration/CouchbaseIntegrationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public virtual async Task Setup()
_config["Couchbase:Username"],
_config["Couchbase:Password"]);

await _cluster.WaitUntilReadyAsync(TimeSpan.FromSeconds(30));

var allBuckets = await _cluster.Buckets.GetAllBucketsAsync();
var doesBucketExist = allBuckets.Any(b => b.Key == _config["Couchbase:BucketName"]);
if (!doesBucketExist)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public override async Task Setup()
_authService = AuthServiceHelper.Create();
_getProfileHandler = new GetProfileHandler(new UserDataService(_usersCollectionProvider, _authService),
new GetProfileRequestValidator(),
new FollowsDataService(_followsCollectionProvider, _authService));
new FollowsDataService(_followsCollectionProvider, _authService),
_authService);
}

[Test]
Expand Down
77 changes: 76 additions & 1 deletion Conduit/Conduit.Tests/TestHelpers/Data/ArticleHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using Conduit.Web.DataAccess.Models;
using Conduit.Web.DataAccess.Providers;
using Conduit.Web.Articles.Services;
using Conduit.Web.Extensions;
using Slugify;

namespace Conduit.Tests.TestHelpers.Data;

Expand All @@ -19,8 +22,80 @@ public static async Task AssertExists(this IConduitArticlesCollectionProvider @t
if (assertions != null)
assertions(articleInDatabaseObj);

// if we made it this far, the user was retrieved
// if we made it this far, the article was retrieved
// and the assertions passed
Assert.That(true);
}

public static async Task AssertExists(this IConduitFavoritesCollectionProvider @this, string username,
Action<List<string>>? assertions = null)
{
var collection = await @this.GetCollectionAsync();
var favoritesInDatabase = await collection.GetAsync($"{username}::favorites");
var favoritesInDatabaseObj = favoritesInDatabase.ContentAs<List<string>>();

if (assertions != null)
assertions(favoritesInDatabaseObj);

// if we made it this far, the favorites was retrieved
// and the assertions passed
Assert.That(true);
}

public static async Task<Article> CreateArticleInDatabase(this IConduitArticlesCollectionProvider @this,
int? favoritesCount = null,
DateTimeOffset? createdAt = null,
string? title = null,
string? description = null,
string? body = null,
string? slug = null,
List<string>? tagList = null,
string? authorUsername = null,
bool? favorited = null)
{
var collection = await @this.GetCollectionAsync();

var article = CreateArticle(favoritesCount, createdAt, title, description, body, slug, tagList, authorUsername,
favorited);

await collection.InsertAsync(article.Slug, article);

return article;
}

private static Article CreateArticle(
int? favoritesCount = null,
DateTimeOffset? createdAt = null,
string? title = null,
string? description = null,
string? body = null,
string? slug = null,
List<string>? tagList = null,
string? authorUsername = null,
bool? favorited = null)
{
var random = new Random();
favoritesCount ??= (int)random.NextInt64(0, 100);
createdAt ??= DateTimeOffset.Now;
title ??= "Title " + random.String(20);
description ??= "Description " + random.String(256);
authorUsername ??= "user-" + random.String(8);
body ??= "Body " + random.String(10000);
slug ??= new SlugService(new SlugHelper()).GenerateSlug(title);
tagList ??= new List<string> { "Couchbase", "baseball"};
favorited ??= true;

var article = new Article();
article.FavoritesCount = favoritesCount.Value;
article.CreatedAt = createdAt.Value;
article.Title = title;
article.Description = description;
article.Body = body;
article.Slug = slug;
article.TagList = tagList;
article.AuthorUsername = authorUsername;
article.Favorited = favorited.Value;

return article;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Conduit.Web.Articles.Handlers;
using Conduit.Web.Articles.ViewModels;
using Conduit.Web.Extensions;

namespace Conduit.Tests.TestHelpers.Dto;

Expand Down
Loading

0 comments on commit dba7543

Please sign in to comment.