Skip to content

Commit

Permalink
Added a retailers controller
Browse files Browse the repository at this point in the history
  • Loading branch information
davewalker5 committed Nov 12, 2023
1 parent 6f163fa commit 35bf8e8
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 18 deletions.
119 changes: 119 additions & 0 deletions src/MusicCatalogue.Api/Controllers/RetailersController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MusicCatalogue.Entities.Database;
using MusicCatalogue.Entities.Exceptions;
using MusicCatalogue.Entities.Interfaces;

namespace MusicCatalogue.Api.Controllers
{
[Authorize]
[ApiController]
[ApiConventionType(typeof(DefaultApiConventions))]
[Route("[controller]")]
public class RetailersController : Controller
{
private readonly IMusicCatalogueFactory _factory;

public RetailersController(IMusicCatalogueFactory factory)
{
_factory = factory;
}

/// <summary>
/// Return a list of all the retailers in the catalogue
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("")]
public async Task<ActionResult<List<Retailer>>> GetRetailersAsync()
{
// Get a list of all artists in the catalogue
List<Retailer> retailers = await _factory.Retailers.ListAsync(x => true);

// If there are no artists, return a no content response
if (!retailers.Any())
{
return NoContent();
}

return retailers;
}

/// <summary>
/// Return retailer details given a retailer ID
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet]
[Route("{id:int}")]
public async Task<ActionResult<Retailer>> GetRetailerByIdAsync(int id)
{
var retailer = await _factory.Retailers.GetAsync(x => x.Id == id);

if (retailer == null)
{
return NotFound();
}

return retailer;
}

/// <summary>
/// Add a retailer to the catalogue
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpPost]
[Route("")]
public async Task<ActionResult<Retailer>> AddRetailerAsync([FromBody] Retailer template)
{
var retailer = await _factory.Retailers.AddAsync(template.Name);
return retailer;
}

/// <summary>
/// Update an existing retailer
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpPut]
[Route("")]
public async Task<ActionResult<Retailer?>> UpdateRetailerAsync([FromBody] Retailer template)
{
var retailer = await _factory.Retailers.UpdateAsync(template.Id, template.Name);
return retailer;
}

/// <summary>
/// Delete an existing retailer
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete]
[Route("{id}")]
public async Task<IActionResult> DeleteRetailerAsync(int id)
{
// Make sure the retailer exists
var retailer = await _factory.Retailers.GetAsync(x => x.Id == id);

// If the retailer doesn't exist, return a 404
if (retailer == null)
{
return NotFound();
}

try
{
// Delete the retailer
await _factory.Retailers.DeleteAsync(id);
}
catch (RetailerInUseException)
{
// Retailer is in use so this is a bad request
return BadRequest();
}

return Ok();
}
}
}
32 changes: 32 additions & 0 deletions src/MusicCatalogue.Entities/Exceptions/RetailerInUseException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;

namespace MusicCatalogue.Entities.Exceptions
{

[Serializable]
[ExcludeFromCodeCoverage]
public class RetailerInUseException : Exception
{
public RetailerInUseException()
{
}

public RetailerInUseException(string message) : base(message)
{
}

public RetailerInUseException(string message, Exception inner) : base(message, inner)
{
}

protected RetailerInUseException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext)
{
}

public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
}
}
2 changes: 2 additions & 0 deletions src/MusicCatalogue.Entities/Interfaces/IRetailerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ public interface IRetailerManager
Task<Retailer> AddAsync(string name);
Task<Retailer> GetAsync(Expression<Func<Retailer, bool>> predicate);
Task<List<Retailer>> ListAsync(Expression<Func<Retailer, bool>> predicate);
Task<Retailer?> UpdateAsync(int retailerId, string name);
Task DeleteAsync(int retailerId);
}
}
65 changes: 57 additions & 8 deletions src/MusicCatalogue.Logic/Database/RetailerManager.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
using Microsoft.EntityFrameworkCore;
using MusicCatalogue.Data;
using MusicCatalogue.Entities.Database;
using MusicCatalogue.Entities.Exceptions;
using MusicCatalogue.Entities.Interfaces;
using MusicCatalogue.Logic.Factory;
using System.Linq.Expressions;

namespace MusicCatalogue.Logic.Database
{
internal class RetailerManager : DatabaseManagerBase, IRetailerManager
internal class RetailerManager : IRetailerManager
{
internal RetailerManager(MusicCatalogueDbContext context) : base(context)
private readonly MusicCatalogueFactory _factory;
private readonly MusicCatalogueDbContext? _context;

internal RetailerManager(MusicCatalogueFactory factory)
{
_factory = factory;
_context = factory.Context as MusicCatalogueDbContext;
}

/// <summary>
Expand All @@ -32,10 +39,10 @@ public async Task<Retailer> GetAsync(Expression<Func<Retailer, bool>> predicate)
/// <param name="predicate"></param>
/// <returns></returns>
public async Task<List<Retailer>> ListAsync(Expression<Func<Retailer, bool>> predicate)
=> await _context.Retailers
.Where(predicate)
.OrderBy(x => x.Name)
.ToListAsync();
=> await _context!.Retailers
.Where(predicate)
.OrderBy(x => x.Name)
.ToListAsync();

/// <summary>
/// Add a retailer, if they doesn't already exist
Expand All @@ -53,11 +60,53 @@ public async Task<Retailer> AddAsync(string name)
{
Name = clean
};
await _context.Retailers.AddAsync(retailer);
await _context.SaveChangesAsync();
await _context!.Retailers.AddAsync(retailer);
await _context!.SaveChangesAsync();
}

return retailer;
}

/// <summary>
/// Update a retailer given their ID
/// </summary>
/// <param name="retailerId"></param>
/// <param name="name"></param>
/// <returns></returns>
public async Task<Retailer?> UpdateAsync(int retailerId, string name)
{
var retailer = await GetAsync(a => a.Id == retailerId);
if (retailer != null)
{
retailer.Name = StringCleaner.Clean(name)!;
await _context!.SaveChangesAsync();
}
return retailer;
}

/// <summary>
/// Delete a retailer given their ID
/// </summary>
/// <param name="retailerId"></param>
/// <returns></returns>
public async Task DeleteAsync(int retailerId)
{
// Check the retailer exists
var retailer = await GetAsync(a => a.Id == retailerId);
if (retailer != null)
{
// Check the retailer isn't in use
var albums = await _factory.Albums.ListAsync(x => x.RetailerId == retailerId);
if (albums.Any())
{
var message = $"Retailer '{retailer.Name} with Id {retailerId} is in use and cannot be deleted";
throw new RetailerInUseException(message);
}

// Delete the retailer
_context!.Retailers.Remove(retailer);
await _context.SaveChangesAsync();
}
}
}
}
2 changes: 1 addition & 1 deletion src/MusicCatalogue.Logic/Factory/MusicCatalogueFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public MusicCatalogueFactory(MusicCatalogueDbContext context)
_artists = new Lazy<IArtistManager>(() => new ArtistManager(context));
_albums = new Lazy<IAlbumManager>(() => new AlbumManager(this));
_tracks = new Lazy<ITrackManager>(() => new TrackManager(context));
_retailers = new Lazy<IRetailerManager>(() => new RetailerManager(context));
_retailers = new Lazy<IRetailerManager>(() => new RetailerManager(this));
_jobStatuses = new Lazy<IJobStatusManager>(() => new JobStatusManager(context));
_users = new Lazy<IUserManager>(() => new UserManager(context));
_importer = new Lazy<IImporter>(() => new CsvImporter(this));
Expand Down
73 changes: 64 additions & 9 deletions src/MusicCatalogue.Tests/RetailerManagerTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using MusicCatalogue.Data;
using MusicCatalogue.Entities.Exceptions;
using MusicCatalogue.Entities.Interfaces;
using MusicCatalogue.Logic.Factory;

Expand All @@ -7,30 +8,38 @@ namespace MusicCatalogue.Tests
[TestClass]
public class RetailerManagerTest
{
private const string ArtistName = "John Coltrane";
private const string AlbumTitle = "Blue Train";
private const int Released = 1957;
private const string Genre = "Jazz";
private const string CoverUrl = "https://some.host/blue-train.jpg";
private const string TrackTitle = "Blue Train";
private const string Name = "Dig Vinyl";
private const string UpdatedName = "Truck Store";

private IRetailerManager? _manager = null;
private IMusicCatalogueFactory _factory = null;

Check warning on line 20 in src/MusicCatalogue.Tests/RetailerManagerTest.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
private int _retailerId;

[TestInitialize]
public void TestInitialize()
{
MusicCatalogueDbContext context = MusicCatalogueDbContextFactory.CreateInMemoryDbContext();
_manager = new MusicCatalogueFactory(context).Retailers;
Task.Run(() => _manager.AddAsync(Name)).Wait();
_factory = new MusicCatalogueFactory(context);
_retailerId = Task.Run(() => _factory.Retailers.AddAsync(Name)).Result.Id;
}

[TestMethod]
public async Task AddDuplicateTest()
{
await _manager!.AddAsync(Name);
var retailers = await _manager.ListAsync(x => true);
await _factory!.Retailers.AddAsync(Name);
var retailers = await _factory.Retailers.ListAsync(x => true);
Assert.AreEqual(1, retailers.Count);
}

[TestMethod]
public async Task AddAndGetTest()
{
var retailer = await _manager!.GetAsync(a => a.Name == Name);
var retailer = await _factory!.Retailers.GetAsync(a => a.Name == Name);
Assert.IsNotNull(retailer);
Assert.IsTrue(retailer.Id > 0);
Assert.AreEqual(Name, retailer.Name);
Expand All @@ -39,23 +48,69 @@ public async Task AddAndGetTest()
[TestMethod]
public async Task GetMissingTest()
{
var retailer = await _manager!.GetAsync(a => a.Name == "Missing");
var retailer = await _factory!.Retailers.GetAsync(a => a.Name == "Missing");
Assert.IsNull(retailer);
}

[TestMethod]
public async Task ListAllTest()
{
var retailers = await _manager!.ListAsync(x => true);
var retailers = await _factory!.Retailers.ListAsync(x => true);
Assert.AreEqual(1, retailers!.Count);
Assert.AreEqual(Name, retailers.First().Name);
}

[TestMethod]
public async Task ListMissingTest()
{
var retailers = await _manager!.ListAsync(e => e.Name == "Missing");
var retailers = await _factory!.Retailers.ListAsync(e => e.Name == "Missing");
Assert.AreEqual(0, retailers!.Count);
}

[TestMethod]
public async Task UpdateTest()
{
var retailer = await _factory!.Retailers.UpdateAsync(_retailerId, UpdatedName);
Assert.IsNotNull(retailer);
Assert.IsTrue(retailer.Id > 0);
Assert.AreEqual(UpdatedName, retailer.Name);
}

[TestMethod]
public async Task UpdateMissingTest()
{
var retailer = await _factory!.Retailers.UpdateAsync(-1, UpdatedName);
Assert.IsNull(retailer);
}

[TestMethod]
public async Task DeleteTest()
{
await _factory!.Retailers.DeleteAsync(_retailerId);
var retailers = await _factory!.Retailers.ListAsync(x => true);
Assert.IsNotNull(retailers);
Assert.IsFalse(retailers.Any());
}

[TestMethod]
public async Task DeleteMissingTest()
{
await _factory!.Retailers.DeleteAsync(-1);
var retailers = await _factory!.Retailers.ListAsync(x => true);
Assert.AreEqual(1, retailers!.Count);
Assert.AreEqual(Name, retailers.First().Name);
}

[TestMethod]
[ExpectedException(typeof(RetailerInUseException))]
public async Task DeleteInUseTest()
{
// Add an album that uses the retailer
var artist = await _factory.Artists.AddAsync(ArtistName);
await _factory.Albums.AddAsync(artist.Id, AlbumTitle, Released, Genre, CoverUrl, false, null, null, _retailerId);

// Now try to delete the retailer - this should raise an exception
await _factory!.Retailers.DeleteAsync(_retailerId);
}
}
}

0 comments on commit 35bf8e8

Please sign in to comment.