Use interfaces to make mocking and testing easier

This commit is contained in:
Jack Hadrill 2020-09-21 22:17:57 +01:00
parent 4078a211da
commit 0c29d79c2b
11 changed files with 236 additions and 86 deletions

View File

@ -0,0 +1,8 @@
using System;
namespace VCinemaApi.Hubs
{
public interface IVCinemaHub
{
}
}

View File

@ -5,107 +5,41 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using VCinemaApi.Models; using VCinemaApi.Models;
using VCinemaApi.Repositories;
namespace VCinemaApi.Hubs namespace VCinemaApi.Hubs
{ {
public class VCinemaHub : Hub public class VCinemaHub : Hub<IVCinemaHub>
{ {
private readonly VCinemaContext _context; private readonly IScreenRepository _screenRepository;
private readonly IWatcherRepository _watcherRepository;
public VCinemaHub(VCinemaContext context) public VCinemaHub(IScreenRepository screenRepository, IWatcherRepository watcherRepository)
{ {
_context = context; _screenRepository = screenRepository;
_watcherRepository = watcherRepository;
} }
public override async Task OnConnectedAsync() public override async Task OnConnectedAsync()
{ {
var watcher = new Watcher() await _watcherRepository.CreateWatcher(Context.ConnectionId);
{
ConnectionId = Context.ConnectionId,
};
await _context.Watchers.AddAsync(watcher);
await _context.SaveChangesAsync();
await base.OnConnectedAsync(); await base.OnConnectedAsync();
} }
public override async Task OnDisconnectedAsync(Exception exception) public override async Task OnDisconnectedAsync(Exception exception)
{ {
Watcher watcher = await _context.Watchers.SingleOrDefaultAsync(watcher => watcher.ConnectionId == Context.ConnectionId); await _watcherRepository.DeleteWatcher(Context.ConnectionId);
_context.Watchers.Remove(watcher);
await _context.SaveChangesAsync();
await base.OnDisconnectedAsync(exception); await base.OnDisconnectedAsync(exception);
} }
public async Task GetScreens() public async Task<IEnumerable<Screen>> GetScreens()
{ {
List<Screen> screens = await _context.Screens.ToListAsync(); return await _screenRepository.GetScreens();
await Clients.Caller.SendAsync("ReceiveScreens", screens);
} }
public async Task GetScreen(int screenId) public async Task<Screen> GetScreenById(int screenId)
{ {
Screen screen = await _context.Screens.SingleOrDefaultAsync(screen => screen.ScreenId == screenId); return await _screenRepository.GetScreenById(screenId);
if (screen == null)
{
throw new HubException($"No screen with ID {screenId} found.");
}
}
public async Task CreateScreen(string name, string source)
{
var screen = new Screen()
{
Name = name,
Source = source
};
await _context.AddAsync<Screen>(screen);
await _context.SaveChangesAsync();
var screens = await _context.Screens.ToListAsync();
await Clients.All.SendAsync("ReceiveScreens", screens);
}
public async Task JoinScreen(int screenId)
{
Screen screen = await _context.Screens.SingleOrDefaultAsync(screen => screen.ScreenId == screenId);
if (screen == null)
{
throw new HubException($"No screen with ID {screenId} found.");
}
Watcher watcher = await _context.Watchers.SingleOrDefaultAsync(watcher => watcher.ConnectionId == Context.ConnectionId);
if (watcher == null)
{
throw new HubException($"No watcher with connection ID {Context.ConnectionId} found.");
}
watcher.Screen = screen;
await _context.SaveChangesAsync();
await Groups.AddToGroupAsync(Context.ConnectionId, screenId.ToString());
await Clients.Caller.SendAsync("ReceiveScreen", screen);
/* Send new watcher notification of watchers to screen. */
await Clients.Group(screenId.ToString()).SendAsync("NewWatcher", watcher);
}
public async Task LeaveScreen()
{
Watcher watcher = await _context.Watchers.SingleOrDefaultAsync(watcher => watcher.ConnectionId == Context.ConnectionId);
if (watcher == null)
{
throw new HubException($"No watcher with connection ID {Context.ConnectionId} found.");
}
int screenId = watcher.Screen.ScreenId;
if (screenId == 0)
{
throw new HubException($"Watcher is not joined to any screens.");
}
await Groups.RemoveFromGroupAsync(Context.ConnectionId, screenId.ToString());
} }
} }
} }

View File

@ -9,9 +9,10 @@ namespace VCinemaApi.Models
[Key] [Key]
public int WatcherId { get; set; } public int WatcherId { get; set; }
public string Name { get; set; } [Required]
public string ConnectionId { get; set; } public string ConnectionId { get; set; }
public string Name { get; set; }
public Screen Screen { get; set; } public Screen Screen { get; set; }
} }
} }

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public interface IScreenRepository
{
Task<List<Screen>> GetScreens();
Task<Screen> GetScreenById(int screenId);
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public interface IWatcherRepository
{
Task<List<Watcher>> GetWatchersByScreenId(int screenId);
Task<Watcher> GetWatcherByConnectionId(string connectionId);
Task CreateWatcher(string connectionId);
Task DeleteWatcher(string connectionId);
}
}

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public class MockScreenRepository : IScreenRepository
{
public Task<List<Screen>> GetScreens()
{
var screens = new List<Screen>
{
new Screen {
ScreenId = 1,
Name = "Kirby's screen",
Source = "https://vcinema.b-cdn.net/shrek.mp4",
PlayStateUpdated = new DateTime(2020, 9, 21, 21, 02, 57),
PlayState = true,
PlayOffset = 1337
},
new Screen {
ScreenId = 2,
Name = "Sid's screen",
Source = "https://vcinema.b-cdn.net/weeb.mp4",
PlayStateUpdated = new DateTime(2020, 9, 21, 21, 03, 22),
PlayState = true,
PlayOffset = 69
}
};
return Task.FromResult<List<Screen>>(screens);
}
public Task<Screen> GetScreenById(int screenId)
{
var screen = new Screen
{
ScreenId = 1,
Name = "Kirby's screen",
Source = "https://vcinema.b-cdn.net/shrek.mp4",
PlayStateUpdated = DateTime.UtcNow,
PlayState = true,
PlayOffset = 1337
};
return Task.FromResult<Screen>(screen);
}
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public class MockWatcherRepository : IWatcherRepository
{
public Task<List<Watcher>> GetWatchersByScreenId(int screenId)
{
var watchers = new List<Watcher>
{
new Watcher
{
WatcherId = 1,
ConnectionId = "dJSbEc73n6YjGIhj-SZz1Q",
Name = "Kirby",
Screen = new Screen {
ScreenId = 1,
Name = "Kirby's screen",
Source = "https://vcinema.b-cdn.net/shrek.mp4",
PlayStateUpdated = new DateTime(2020, 9, 21, 21, 02, 57),
PlayState = true,
PlayOffset = 1337
}
},
new Watcher
{
WatcherId = 2,
ConnectionId = "rNA9Jn7ytYPzQfFJ-j3NBa",
Name = "Sid",
Screen = new Screen {
ScreenId = 2,
Name = "Sid's screen",
Source = "https://vcinema.b-cdn.net/weeb.mp4",
PlayStateUpdated = new DateTime(2020, 9, 21, 21, 03, 22),
PlayState = true,
PlayOffset = 69
}
}
};
return Task.FromResult<List<Watcher>>(watchers);
}
public Task<Watcher> GetWatcherByConnectionId(string connectionId)
{
var watcher = new Watcher
{
WatcherId = 1,
ConnectionId = "dJSbEc73n6YjGIhj-SZz1Q",
Name = "Kirby",
Screen = new Screen
{
ScreenId = 1,
Name = "Kirby's screen",
Source = "https://vcinema.b-cdn.net/shrek.mp4",
PlayStateUpdated = new DateTime(2020, 9, 21, 21, 02, 57),
PlayState = true,
PlayOffset = 1337
}
};
return Task.FromResult<Watcher>(watcher);
}
public Task CreateWatcher(string connectionId)
{
throw new NotImplementedException();
}
public Task DeleteWatcher(string connectionId)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,10 +1,27 @@
using System; using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories namespace VCinemaApi.Repositories
{ {
public class ScreenRepository public class ScreenRepository : IScreenRepository
{ {
public ScreenRepository() private readonly VCinemaContext _context;
public ScreenRepository(VCinemaContext context)
{ {
_context = context;
}
public Task<List<Screen>> GetScreens()
{
return _context.Screens.ToListAsync();
}
public Task<Screen> GetScreenById(int screenId)
{
return _context.Screens.SingleOrDefaultAsync(screen => screen.ScreenId == screenId);
} }
} }
} }

View File

@ -1,10 +1,46 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories namespace VCinemaApi.Repositories
{ {
public class WatcherRepository public class WatcherRepository : IWatcherRepository
{ {
public WatcherRepository() private readonly VCinemaContext _context;
public WatcherRepository(VCinemaContext context)
{ {
_context = context;
}
public Task<List<Watcher>> GetWatchersByScreenId(int screenId)
{
throw new NotImplementedException();
}
public Task<Watcher> GetWatcherByConnectionId(string connectionId)
{
return _context.Watchers.SingleOrDefaultAsync(watcher => watcher.ConnectionId == connectionId);
}
public async Task CreateWatcher(string connectionId)
{
var watcher = new Watcher()
{
ConnectionId = connectionId,
};
await _context.Watchers.AddAsync(watcher);
await _context.SaveChangesAsync();
}
public async Task DeleteWatcher(string connectionId)
{
var watcher = await GetWatcherByConnectionId(connectionId);
_context.Watchers.Remove(watcher);
await _context.SaveChangesAsync();
} }
} }
} }

View File

@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using VCinemaApi.Hubs; using VCinemaApi.Hubs;
using VCinemaApi.Models; using VCinemaApi.Models;
using VCinemaApi.Repositories;
namespace VCinemaApi namespace VCinemaApi
{ {
@ -21,7 +22,9 @@ namespace VCinemaApi
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddDbContext<VCinemaContext>(options => options.UseInMemoryDatabase("VCinema")); services.AddDbContext<VCinemaContext>(options => options.UseInMemoryDatabase("VCinema"));
services.AddScoped<IScreenRepository, ScreenRepository>();
services.AddScoped<IWatcherRepository, WatcherRepository>();
services.AddControllers(); services.AddControllers();
services.AddSignalR(); services.AddSignalR();
} }

View File

@ -5,6 +5,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(RunConfiguration)' == 'VCinema' " />
<ItemGroup> <ItemGroup>
<Folder Include="Models\" /> <Folder Include="Models\" />
<Folder Include="Hubs\" /> <Folder Include="Hubs\" />