Skip to content

Commit db73bdf

Browse files
committed
#2330: Get UserAccessContext from Game/TournamentGame for photo management
1 parent fbc8633 commit db73bdf

10 files changed

Lines changed: 56 additions & 38 deletions

File tree

CourageScores.Tests/Services/Command/DeletePhotoCommandTests.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using AutoFixture;
22
using CourageScores.Models;
33
using CourageScores.Models.Cosmos;
4+
using CourageScores.Models.Cosmos.Game;
45
using CourageScores.Services;
56
using CourageScores.Services.Command;
7+
using CourageScores.Services.Identity;
68
using Moq;
79
using NUnit.Framework;
810
using CosmosGame = CourageScores.Models.Cosmos.Game.Game;
@@ -28,6 +30,7 @@ public void SetupEachTest()
2830
};
2931
_game = new CosmosGame
3032
{
33+
Home = new GameTeam(),
3134
Photos =
3235
{
3336
_photoReference
@@ -42,7 +45,7 @@ public void SetupEachTest()
4245
public async Task ApplyUpdate_WhenPhotoCannotBeDeleted_ReturnsUnsuccessful()
4346
{
4447
_photoService
45-
.Setup(s => s.Delete(_photoReference.Id, _token))
48+
.Setup(s => s.Delete(_photoReference.Id, It.IsAny<UserAccessContext>(), _token))
4649
.ReturnsAsync(new ActionResult<Photo>
4750
{
4851
Warnings =
@@ -62,7 +65,7 @@ public async Task ApplyUpdate_WhenPhotoCannotBeDeleted_ReturnsUnsuccessful()
6265
public async Task ApplyUpdate_WhenPhotoHasBeenDeleted_RemovesPhotoFromGame()
6366
{
6467
_photoService
65-
.Setup(s => s.Delete(_photoReference.Id, _token))
68+
.Setup(s => s.Delete(_photoReference.Id, It.IsAny<UserAccessContext>(), _token))
6669
.ReturnsAsync(new ActionResult<Photo>
6770
{
6871
Success = true,

CourageScores.Tests/Services/Command/UploadPhotoCommandTests.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public async Task ApplyUpdate_GivenFile_CallsPhotoServiceCorrectly()
9898
var unsuccessful = new ActionResult<PhotoReference>(); // success = false
9999
var photo = new Parameter<Photo>();
100100
_photoService
101-
.Setup(s => s.Upsert(It.Is<Photo>(p => photo.Capture(p)), _token))
101+
.Setup(s => s.Upsert(It.Is<Photo>(p => photo.Capture(p)), It.IsAny<UserAccessContext>(), _token))
102102
.ReturnsAsync(unsuccessful);
103103
var teamId = Guid.NewGuid();
104104
var fileName = "FILE.png";
@@ -109,7 +109,7 @@ public async Task ApplyUpdate_GivenFile_CallsPhotoServiceCorrectly()
109109

110110
await _command.ApplyUpdate(_game, _token);
111111

112-
_photoService.Verify(s => s.Upsert(It.IsAny<Photo>(), _token));
112+
_photoService.Verify(s => s.Upsert(It.IsAny<Photo>(), It.IsAny<UserAccessContext>(), _token));
113113
Assert.That(photo.Value!.Id, Is.Not.EqualTo(Guid.Empty));
114114
Assert.That(photo.Value!.TeamId, Is.EqualTo(teamId));
115115
Assert.That(photo.Value!.ContentType, Is.EqualTo(contentType));
@@ -127,7 +127,7 @@ public async Task ApplyUpdate_WhenPhotoServiceReturnsFailure_ReturnsPhotoService
127127
Warnings = { "UNSUCCESSFUL" },
128128
};
129129
_photoService
130-
.Setup(s => s.Upsert(It.IsAny<Photo>(), _token))
130+
.Setup(s => s.Upsert(It.IsAny<Photo>(), It.IsAny<UserAccessContext>(), _token))
131131
.ReturnsAsync(unsuccessful);
132132

133133
var result = await _command.ApplyUpdate(_game, _token);
@@ -150,7 +150,7 @@ public async Task ApplyUpdate_WhenGameHasNoPhotos_AddsPhoto()
150150
Result = photoReference,
151151
};
152152
_photoService
153-
.Setup(s => s.Upsert(It.IsAny<Photo>(), _token))
153+
.Setup(s => s.Upsert(It.IsAny<Photo>(), It.IsAny<UserAccessContext>(), _token))
154154
.ReturnsAsync(successful);
155155

156156
var result = await _command.ApplyUpdate(_game, _token);
@@ -174,7 +174,7 @@ public async Task ApplyUpdate_WhenGameHasPhotoFromExistingUser_ReplacesPhoto()
174174
Result = photoReference,
175175
};
176176
_photoService
177-
.Setup(s => s.Upsert(It.IsAny<Photo>(), _token))
177+
.Setup(s => s.Upsert(It.IsAny<Photo>(), It.IsAny<UserAccessContext>(), _token))
178178
.ReturnsAsync(successful);
179179
_game.Photos.Add(new PhotoReference
180180
{
@@ -184,7 +184,7 @@ public async Task ApplyUpdate_WhenGameHasPhotoFromExistingUser_ReplacesPhoto()
184184

185185
var result = await _command.ApplyUpdate(_game, _token);
186186

187-
_photoService.Verify(s => s.Upsert(It.Is<Photo>(p => p.Id == existingPhotoId), _token));
187+
_photoService.Verify(s => s.Upsert(It.Is<Photo>(p => p.Id == existingPhotoId), It.IsAny<UserAccessContext>(), _token));
188188
Assert.That(result.Success, Is.True);
189189
Assert.That(result.Messages, Is.EquivalentTo(["Photo added"]));
190190
Assert.That(_game.Photos, Is.EquivalentTo([photoReference]));
@@ -209,7 +209,7 @@ public async Task ApplyUpdate_WhenGameHasMaxNumberOfPhotosIncludingOneFromUser_R
209209
Author = "ANOTHER USER 1",
210210
};
211211
_photoService
212-
.Setup(s => s.Upsert(It.IsAny<Photo>(), _token))
212+
.Setup(s => s.Upsert(It.IsAny<Photo>(), It.IsAny<UserAccessContext>(), _token))
213213
.ReturnsAsync(successful);
214214
_game.Photos.Add(new PhotoReference
215215
{
@@ -220,7 +220,7 @@ public async Task ApplyUpdate_WhenGameHasMaxNumberOfPhotosIncludingOneFromUser_R
220220

221221
var result = await _command.ApplyUpdate(_game, _token);
222222

223-
_photoService.Verify(s => s.Upsert(It.Is<Photo>(p => p.Id == existingPhotoId), _token));
223+
_photoService.Verify(s => s.Upsert(It.Is<Photo>(p => p.Id == existingPhotoId), It.IsAny<UserAccessContext>(), _token));
224224
Assert.That(result.Success, Is.True);
225225
Assert.That(result.Messages, Is.EquivalentTo(["Photo added"]));
226226
Assert.That(_game.Photos, Is.EquivalentTo([photoReference, otherUserPhoto]));
@@ -240,7 +240,7 @@ public async Task ApplyUpdate_WhenGameHasMaxNumberOfPhotosFromOtherUsers_Returns
240240
Result = photoReference,
241241
};
242242
_photoService
243-
.Setup(s => s.Upsert(It.IsAny<Photo>(), _token))
243+
.Setup(s => s.Upsert(It.IsAny<Photo>(), It.IsAny<UserAccessContext>(), _token))
244244
.ReturnsAsync(successful);
245245
_game.Photos.Add(new PhotoReference
246246
{
@@ -255,7 +255,7 @@ public async Task ApplyUpdate_WhenGameHasMaxNumberOfPhotosFromOtherUsers_Returns
255255

256256
var result = await _command.ApplyUpdate(_game, _token);
257257

258-
_photoService.Verify(s => s.Upsert(It.IsAny<Photo>(), _token), Times.Never);
258+
_photoService.Verify(s => s.Upsert(It.IsAny<Photo>(), It.IsAny<UserAccessContext>(), _token), Times.Never);
259259
Assert.That(result.Success, Is.False);
260260
Assert.That(result.Warnings, Is.EquivalentTo(["No more photos can be added to this entity, maximum photo count reached: 2"]));
261261
}

CourageScores.Tests/Services/PhotoServiceTests.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public async Task Upsert_WhenLoggedOut_ReturnsNotPermitted()
8080
{
8181
_user = null;
8282

83-
var result = await _service.Upsert(_photo, _token);
83+
var result = await _service.Upsert(_photo, UserAccessContext.None(), _token);
8484

8585
Assert.That(result.Success, Is.False);
8686
Assert.That(result.Warnings, Is.EquivalentTo(["Not permitted"]));
@@ -91,7 +91,7 @@ public async Task Upsert_WhenNotPermitted_ReturnsNotPermitted()
9191
{
9292
_access = _access.Without(AccessOption.UploadPhotos);
9393

94-
var result = await _service.Upsert(_photo, _token);
94+
var result = await _service.Upsert(_photo, UserAccessContext.None(), _token);
9595

9696
Assert.That(result.Success, Is.False);
9797
Assert.That(result.Warnings, Is.EquivalentTo(["Not permitted"]));
@@ -103,7 +103,7 @@ public async Task Upsert_When_FeatureDisabled_ReturnsFeatureDisabled()
103103
_featureState.ConfiguredValue = "false";
104104
_access = _access.With(AccessOption.UploadPhotos);
105105

106-
var result = await _service.Upsert(_photo, _token);
106+
var result = await _service.Upsert(_photo, UserAccessContext.None(), _token);
107107

108108
Assert.That(result.Success, Is.False);
109109
Assert.That(result.Warnings, Is.EquivalentTo(["Feature disabled"]));
@@ -120,7 +120,7 @@ public async Task Upsert_GivenPhoto_ResizesThenStoresPhoto()
120120
};
121121
_photoHelper.Setup(h => h.ResizePhoto(_photo.PhotoBytes, _settings.MaxPhotoHeight, _token)).ReturnsAsync(resizeResult);
122122

123-
var result = await _service.Upsert(_photo, _token);
123+
var result = await _service.Upsert(_photo, UserAccessContext.None(), _token);
124124

125125
_photoRepository.Verify(r => r.Upsert(_photo, _token));
126126
_photoRepository.Verify(r => r.Upsert(It.Is<Photo>(p => p.PhotoBytes == _resizedBytes), _token));
@@ -146,7 +146,7 @@ public async Task Upsert_GivenNonPhotoFile_ReturnsUnsuccessful()
146146
};
147147
_photoHelper.Setup(h => h.ResizePhoto(_photo.PhotoBytes, _settings.MaxPhotoHeight, _token)).ReturnsAsync(resizeResult);
148148

149-
var result = await _service.Upsert(_photo, _token);
149+
var result = await _service.Upsert(_photo, UserAccessContext.None(), _token);
150150

151151
_photoRepository.Verify(r => r.Upsert(It.IsAny<Photo>(), _token), Times.Never);
152152
Assert.That(result.Success, Is.False);
@@ -168,7 +168,7 @@ public async Task Upsert_GivenNewPhoto_SetsAuthorAndEditor()
168168
_photo.Updated = default;
169169
_photoHelper.Setup(h => h.ResizePhoto(_photo.PhotoBytes, _settings.MaxPhotoHeight, _token)).ReturnsAsync(resizeResult);
170170

171-
await _service.Upsert(_photo, _token);
171+
await _service.Upsert(_photo, UserAccessContext.None(), _token);
172172

173173
_photoRepository.Verify(r => r.Upsert(It.Is<Photo>(p => p.Author == _user!.Name && p.Editor == _user.Name && p.Created == _now.UtcDateTime && p.Updated == _now.UtcDateTime), _token));
174174
}
@@ -189,7 +189,7 @@ public async Task Upsert_GivenReplacementPhoto_SetsEditor()
189189
_photo.Editor = null!;
190190
_photo.Updated = default;
191191

192-
await _service.Upsert(_photo, _token);
192+
await _service.Upsert(_photo, UserAccessContext.None(), _token);
193193

194194
_photoRepository.Verify(r => r.Upsert(It.Is<Photo>(p => p.Author == "AUTHOR" && p.Editor == _user!.Name && p.Created == created && p.Updated == _now.UtcDateTime), _token));
195195
}
@@ -265,7 +265,7 @@ public async Task Delete_WhenLoggedOut_ReturnsNotPermitted()
265265
{
266266
_user = null;
267267

268-
var result = await _service.Delete(_existingPhoto.Id, _token);
268+
var result = await _service.Delete(_existingPhoto.Id, UserAccessContext.None(), _token);
269269

270270
Assert.That(result.Success, Is.False);
271271
Assert.That(result.Warnings, Is.EquivalentTo(["Not permitted"]));
@@ -276,7 +276,7 @@ public async Task Delete_WhenNotAdmin_ReturnsNotPermitted()
276276
{
277277
_access = _access.Without(AccessOption.DeleteAnyPhoto);
278278

279-
var result = await _service.Delete(_existingPhoto.Id, _token);
279+
var result = await _service.Delete(_existingPhoto.Id, UserAccessContext.None(), _token);
280280

281281
Assert.That(result.Success, Is.False);
282282
Assert.That(result.Warnings, Is.EquivalentTo(["Not permitted"]));
@@ -288,7 +288,7 @@ public async Task Delete_WhenFeatureDisabled_ReturnsFeatureDisabled()
288288
_featureState.ConfiguredValue = "false";
289289
_access = _access.With(AccessOption.DeleteAnyPhoto);
290290

291-
var result = await _service.Delete(_existingPhoto.Id, _token);
291+
var result = await _service.Delete(_existingPhoto.Id, UserAccessContext.None(), _token);
292292

293293
Assert.That(result.Success, Is.False);
294294
Assert.That(result.Warnings, Is.EquivalentTo(["Feature disabled"]));
@@ -299,7 +299,7 @@ public async Task Delete_WhenPhotoNotFound_ReturnsNotFound()
299299
{
300300
_access = _access.With(AccessOption.DeleteAnyPhoto);
301301

302-
var result = await _service.Delete(Guid.NewGuid(), _token);
302+
var result = await _service.Delete(Guid.NewGuid(), UserAccessContext.None(), _token);
303303

304304
Assert.That(result.Success, Is.False);
305305
Assert.That(result.Warnings, Is.EquivalentTo(["Not found"]));
@@ -311,7 +311,7 @@ public async Task Delete_WhenOnlyAbleToUploadPhotosAndPhotoIsFromADifferentUser_
311311
_access = _access.With(AccessOption.UploadPhotos).Without(AccessOption.DeleteAnyPhoto);
312312
_existingPhoto.Author = "ANOTHER USER";
313313

314-
var result = await _service.Delete(_existingPhoto.Id, _token);
314+
var result = await _service.Delete(_existingPhoto.Id, UserAccessContext.None(), _token);
315315

316316
Assert.That(result.Success, Is.False);
317317
Assert.That(result.Warnings, Is.EquivalentTo(["You can only delete your own photos"]));
@@ -323,7 +323,7 @@ public async Task Delete_WhenOnlyAbleToUploadPhotosAndPhotoIsFromSelf_DeletesPho
323323
_access = _access.With(AccessOption.UploadPhotos);
324324
_existingPhoto.Author = _user!.Name;
325325

326-
var result = await _service.Delete(_existingPhoto.Id, _token);
326+
var result = await _service.Delete(_existingPhoto.Id, UserAccessContext.None(), _token);
327327

328328
Assert.That(result.Success, Is.True);
329329
Assert.That(result.Result, Is.EqualTo(_existingPhoto));
@@ -339,7 +339,7 @@ public async Task Delete_WhenAdminAndPhotoIsFromADifferentUser_DeletesPhoto()
339339
_access = _access.Without(AccessOption.UploadPhotos).With(AccessOption.DeleteAnyPhoto);
340340
_existingPhoto.Author = "ANOTHER USER";
341341

342-
var result = await _service.Delete(_existingPhoto.Id, _token);
342+
var result = await _service.Delete(_existingPhoto.Id, UserAccessContext.None(), _token);
343343

344344
Assert.That(result.Success, Is.True);
345345
Assert.That(result.Success, Is.True);
@@ -356,7 +356,7 @@ public async Task Delete_WhenAdminAndPhotoIsFromSelf_DeletesPhoto()
356356
_access = _access.With(AccessOption.DeleteAnyPhoto);
357357
_existingPhoto.Author = _user!.Name;
358358

359-
var result = await _service.Delete(_existingPhoto.Id, _token);
359+
var result = await _service.Delete(_existingPhoto.Id, UserAccessContext.None(), _token);
360360

361361
Assert.That(result.Success, Is.True);
362362
Assert.That(result.Success, Is.True);

CourageScores/Models/Cosmos/Game/Game.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ public async Task<bool> CanDelete(IUserAccessService userAccess, CancellationTok
165165
return await userAccess.HasAccess(AccessOption.ManageGames, token);
166166
}
167167

168+
public UserAccessContext GetUserAccessContext()
169+
{
170+
return UserAccessContext.ForTeam(SeasonId, DivisionId, Home.Id);
171+
}
172+
168173
private class GameScoreVisitor : IGameVisitor, IGameVisitable
169174
{
170175
private readonly GameTeam _away;

CourageScores/Models/Cosmos/Game/TournamentGame.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,11 @@ public async Task<bool> CanDelete(IUserAccessService userAccess, CancellationTok
7373
{
7474
return await userAccess.HasAccess(AccessOption.ManageTournaments, token);
7575
}
76+
77+
public UserAccessContext GetUserAccessContext()
78+
{
79+
return DivisionId != null
80+
? UserAccessContext.ForDivision(SeasonId, DivisionId.Value)
81+
: UserAccessContext.ForSeason(SeasonId);
82+
}
7683
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
using CourageScores.Services.Identity;
2+
13
namespace CourageScores.Models.Cosmos;
24

35
public interface IPhotoEntity
46
{
57
Guid Id { get; } // the entity will likely extend CosmosEntity which will provide the id
68
List<PhotoReference> Photos { get; set; }
7-
}
9+
UserAccessContext GetUserAccessContext();
10+
}

CourageScores/Services/Command/DeletePhotoCommand.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public async Task<ActionResult<T>> ApplyUpdate(T model, CancellationToken token)
2525
{
2626
_photoId.ThrowIfNull($"{nameof(WithId)} must be called first");
2727

28-
var result = await _photoService.Delete(_photoId!.Value, token);
28+
var context = model.GetUserAccessContext();
29+
var result = await _photoService.Delete(_photoId!.Value, context, token);
2930
if (!result.Success)
3031
{
3132
return result.As<T>();
@@ -41,4 +42,4 @@ public async Task<ActionResult<T>> ApplyUpdate(T model, CancellationToken token)
4142
},
4243
};
4344
}
44-
}
45+
}

CourageScores/Services/Command/UploadPhotoCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public async Task<ActionResult<T>> ApplyUpdate(T model, CancellationToken token)
8686
PhotoBytes = fileContent.ToArray(),
8787
};
8888

89-
var result = await _photoService.Upsert(photo, token);
89+
var result = await _photoService.Upsert(photo, context, token);
9090
if (!result.Success || result.Result == null)
9191
{
9292
return result.As<T>();
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using CourageScores.Models;
22
using CourageScores.Models.Cosmos;
3+
using CourageScores.Services.Identity;
34

45
namespace CourageScores.Services;
56

67
public interface IPhotoService
78
{
8-
Task<ActionResult<PhotoReference>> Upsert(Photo photo, CancellationToken token);
9+
Task<ActionResult<PhotoReference>> Upsert(Photo photo, UserAccessContext context, CancellationToken token);
910
Task<Photo?> GetPhoto(Guid id, CancellationToken token);
10-
Task<ActionResult<Photo>> Delete(Guid id, CancellationToken token);
11-
}
11+
Task<ActionResult<Photo>> Delete(Guid id, UserAccessContext context, CancellationToken token);
12+
}

CourageScores/Services/PhotoService.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@ public PhotoService(
3535
_accessService = accessService;
3636
}
3737

38-
public async Task<ActionResult<PhotoReference>> Upsert(Photo photo, CancellationToken token)
38+
public async Task<ActionResult<PhotoReference>> Upsert(Photo photo, UserAccessContext context, CancellationToken token)
3939
{
4040
var user = await _userService.GetUser(token);
41-
var context = UserAccessContext.NotImplemented("seasonId, divisionId, teamId are not accessible");
4241
if (!await _accessService.HasAccess(user, AccessOption.UploadPhotos, context, token))
4342
{
4443
return Warning<PhotoReference>("Not permitted");
@@ -109,10 +108,9 @@ public async Task<ActionResult<PhotoReference>> Upsert(Photo photo, Cancellation
109108
: null;
110109
}
111110

112-
public async Task<ActionResult<Photo>> Delete(Guid id, CancellationToken token)
111+
public async Task<ActionResult<Photo>> Delete(Guid id, UserAccessContext context, CancellationToken token)
113112
{
114113
var user = await _userService.GetUser(token);
115-
var context = UserAccessContext.NotImplemented("seasonId, divisionId, teamId are not accessible");
116114
var canDeleteAnyPhoto = await _accessService.HasAccess(user, AccessOption.DeleteAnyPhoto, context, token);
117115
var canDeleteOwnPhoto = await _accessService.HasAccess(user, AccessOption.UploadPhotos, context, token);
118116

0 commit comments

Comments
 (0)