Browse Source

Contributor fix.

pull/363/head
Sebastian 7 years ago
parent
commit
9e0c9e64cd
  1. 63
      src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
  2. 4
      src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs
  3. 4
      src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitedResult.cs
  4. 23
      src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
  5. 30
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorAssignedDto.cs
  6. 16
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs
  7. 36
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs
  8. 17
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsMetadata.cs
  9. 7
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs
  10. 15
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  11. 6
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  12. 101
      src/Squidex/app/shared/services/contributors.service.spec.ts
  13. 63
      src/Squidex/app/shared/services/contributors.service.ts
  14. 76
      src/Squidex/app/shared/state/contributors.state.spec.ts
  15. 70
      src/Squidex/app/shared/state/contributors.state.ts
  16. 32
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs
  17. 8
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs

63
src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs

@ -60,11 +60,13 @@ namespace Squidex.Domain.Apps.Entities.Apps
switch (command) switch (command)
{ {
case CreateApp createApp: case CreateApp createApp:
return CreateAsync(createApp, c => return CreateReturnAsync(createApp, async c =>
{ {
GuardApp.CanCreate(c); GuardApp.CanCreate(c);
Create(c); Create(c);
return await GetRawStateAsync();
}); });
case AssignContributor assignContributor: case AssignContributor assignContributor:
@ -74,111 +76,137 @@ namespace Squidex.Domain.Apps.Entities.Apps
AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId)); AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId));
return EntityCreatedResult.Create(c.ContributorId, Version); return await GetRawStateAsync();
}); });
case RemoveContributor removeContributor: case RemoveContributor removeContributor:
return UpdateAsync(removeContributor, c => return UpdateReturnAsync(removeContributor, async c =>
{ {
GuardAppContributors.CanRemove(Snapshot.Contributors, c); GuardAppContributors.CanRemove(Snapshot.Contributors, c);
RemoveContributor(c); RemoveContributor(c);
return await GetRawStateAsync();
}); });
case AttachClient attachClient: case AttachClient attachClient:
return UpdateAsync(attachClient, c => return UpdateReturnAsync(attachClient, async c =>
{ {
GuardAppClients.CanAttach(Snapshot.Clients, c); GuardAppClients.CanAttach(Snapshot.Clients, c);
AttachClient(c); AttachClient(c);
return await GetRawStateAsync();
}); });
case UpdateClient updateClient: case UpdateClient updateClient:
return UpdateAsync(updateClient, c => return UpdateReturnAsync(updateClient, async c =>
{ {
GuardAppClients.CanUpdate(Snapshot.Clients, c, Snapshot.Roles); GuardAppClients.CanUpdate(Snapshot.Clients, c, Snapshot.Roles);
UpdateClient(c); UpdateClient(c);
return await GetRawStateAsync();
}); });
case RevokeClient revokeClient: case RevokeClient revokeClient:
return UpdateAsync(revokeClient, c => return UpdateReturnAsync(revokeClient, async c =>
{ {
GuardAppClients.CanRevoke(Snapshot.Clients, c); GuardAppClients.CanRevoke(Snapshot.Clients, c);
RevokeClient(c); RevokeClient(c);
return await GetRawStateAsync();
}); });
case AddLanguage addLanguage: case AddLanguage addLanguage:
return UpdateAsync(addLanguage, c => return UpdateReturnAsync(addLanguage, async c =>
{ {
GuardAppLanguages.CanAdd(Snapshot.LanguagesConfig, c); GuardAppLanguages.CanAdd(Snapshot.LanguagesConfig, c);
AddLanguage(c); AddLanguage(c);
return await GetRawStateAsync();
}); });
case RemoveLanguage removeLanguage: case RemoveLanguage removeLanguage:
return UpdateAsync(removeLanguage, c => return UpdateReturnAsync(removeLanguage, async c =>
{ {
GuardAppLanguages.CanRemove(Snapshot.LanguagesConfig, c); GuardAppLanguages.CanRemove(Snapshot.LanguagesConfig, c);
RemoveLanguage(c); RemoveLanguage(c);
return await GetRawStateAsync();
}); });
case UpdateLanguage updateLanguage: case UpdateLanguage updateLanguage:
return UpdateAsync(updateLanguage, c => return UpdateReturnAsync(updateLanguage, async c =>
{ {
GuardAppLanguages.CanUpdate(Snapshot.LanguagesConfig, c); GuardAppLanguages.CanUpdate(Snapshot.LanguagesConfig, c);
UpdateLanguage(c); UpdateLanguage(c);
return await GetRawStateAsync();
}); });
case AddRole addRole: case AddRole addRole:
return UpdateAsync(addRole, c => return UpdateReturnAsync(addRole, async c =>
{ {
GuardAppRoles.CanAdd(Snapshot.Roles, c); GuardAppRoles.CanAdd(Snapshot.Roles, c);
AddRole(c); AddRole(c);
return await GetRawStateAsync();
}); });
case DeleteRole deleteRole: case DeleteRole deleteRole:
return UpdateAsync(deleteRole, c => return UpdateReturnAsync(deleteRole, async c =>
{ {
GuardAppRoles.CanDelete(Snapshot.Roles, c, Snapshot.Contributors, Snapshot.Clients); GuardAppRoles.CanDelete(Snapshot.Roles, c, Snapshot.Contributors, Snapshot.Clients);
DeleteRole(c); DeleteRole(c);
return await GetRawStateAsync();
}); });
case UpdateRole updateRole: case UpdateRole updateRole:
return UpdateAsync(updateRole, c => return UpdateReturnAsync(updateRole, async c =>
{ {
GuardAppRoles.CanUpdate(Snapshot.Roles, c); GuardAppRoles.CanUpdate(Snapshot.Roles, c);
UpdateRole(c); UpdateRole(c);
return await GetRawStateAsync();
}); });
case AddPattern addPattern: case AddPattern addPattern:
return UpdateAsync(addPattern, c => return UpdateReturnAsync(addPattern, async c =>
{ {
GuardAppPatterns.CanAdd(Snapshot.Patterns, c); GuardAppPatterns.CanAdd(Snapshot.Patterns, c);
AddPattern(c); AddPattern(c);
return await GetRawStateAsync();
}); });
case DeletePattern deletePattern: case DeletePattern deletePattern:
return UpdateAsync(deletePattern, c => return UpdateReturnAsync(deletePattern, async c =>
{ {
GuardAppPatterns.CanDelete(Snapshot.Patterns, c); GuardAppPatterns.CanDelete(Snapshot.Patterns, c);
DeletePattern(c); DeletePattern(c);
return await GetRawStateAsync();
}); });
case UpdatePattern updatePattern: case UpdatePattern updatePattern:
return UpdateAsync(updatePattern, c => return UpdateReturnAsync(updatePattern, async c =>
{ {
GuardAppPatterns.CanUpdate(Snapshot.Patterns, c); GuardAppPatterns.CanUpdate(Snapshot.Patterns, c);
UpdatePattern(c); UpdatePattern(c);
return await GetRawStateAsync();
}); });
case ChangePlan changePlan: case ChangePlan changePlan:
@ -384,6 +412,11 @@ namespace Squidex.Domain.Apps.Entities.Apps
return new AppContributorAssigned { ContributorId = actor.Identifier, Role = Role.Owner }; return new AppContributorAssigned { ContributorId = actor.Identifier, Role = Role.Owner };
} }
public Task<IAppEntity> GetRawStateAsync()
{
return Task.FromResult<IAppEntity>(Snapshot);
}
public Task<J<IAppEntity>> GetStateAsync() public Task<J<IAppEntity>> GetStateAsync()
{ {
return J.AsTask<IAppEntity>(Snapshot); return J.AsTask<IAppEntity>(Snapshot);

4
src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs

@ -35,9 +35,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
await next(); await next();
if (assignContributor.IsCreated && context.PlainResult is EntityCreatedResult<string> id) if (assignContributor.IsCreated && context.PlainResult is IAppEntity app)
{ {
context.Complete(new InvitedResult { Id = id }); context.Complete(new InvitedResult { App = app });
} }
return; return;

4
src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitedResult.cs

@ -5,12 +5,10 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Invitation namespace Squidex.Domain.Apps.Entities.Apps.Invitation
{ {
public sealed class InvitedResult public sealed class InvitedResult
{ {
public EntityCreatedResult<string> Id { get; set; } public IAppEntity App { get; set; }
} }
} }

23
src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Squidex.Areas.Api.Controllers.Apps.Models; using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Invitation; using Squidex.Domain.Apps.Entities.Apps.Invitation;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
@ -47,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[ApiCosts(0)] [ApiCosts(0)]
public IActionResult GetContributors(string app) public IActionResult GetContributors(string app)
{ {
var response = ContributorsDto.FromApp(App, appPlansProvider); var response = ContributorsDto.FromApp(App, appPlansProvider, this, false);
Response.Headers[HeaderNames.ETag] = App.Version.ToString(); Response.Headers[HeaderNames.ETag] = App.Version.ToString();
@ -66,7 +67,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </returns> /// </returns>
[HttpPost] [HttpPost]
[Route("apps/{app}/contributors/")] [Route("apps/{app}/contributors/")]
[ProducesResponseType(typeof(ContributorAssignedDto), 201)] [ProducesResponseType(typeof(ContributorsDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)] [ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppContributorsAssign)] [ApiPermission(Permissions.AppContributorsAssign)]
[ApiCosts(1)] [ApiCosts(1)]
@ -75,15 +76,15 @@ namespace Squidex.Areas.Api.Controllers.Apps
var command = request.ToCommand(); var command = request.ToCommand();
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);
var response = (ContributorAssignedDto)null; var response = (ContributorsDto)null;
if (context.PlainResult is EntityCreatedResult<string> idOrValue) if (context.PlainResult is IAppEntity newApp)
{ {
response = ContributorAssignedDto.FromId(idOrValue.IdOrValue, false); response = ContributorsDto.FromApp(newApp, appPlansProvider, this, false);
} }
else if (context.PlainResult is InvitedResult invited) else if (context.PlainResult is InvitedResult invited)
{ {
response = ContributorAssignedDto.FromId(invited.Id.IdOrValue, true); response = ContributorsDto.FromApp(invited.App, appPlansProvider, this, true);
} }
return Ok(response); return Ok(response);
@ -95,20 +96,24 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <param name="app">The name of the app.</param> /// <param name="app">The name of the app.</param>
/// <param name="id">The id of the contributor.</param> /// <param name="id">The id of the contributor.</param>
/// <returns> /// <returns>
/// 204 => User removed from app. /// 200 => User removed from app.
/// 400 => User is not assigned to the app. /// 400 => User is not assigned to the app.
/// 404 => Contributor or app not found. /// 404 => Contributor or app not found.
/// </returns> /// </returns>
[HttpDelete] [HttpDelete]
[Route("apps/{app}/contributors/{id}/")] [Route("apps/{app}/contributors/{id}/")]
[ProducesResponseType(typeof(ContributorsDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)] [ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppContributorsRevoke)] [ApiPermission(Permissions.AppContributorsRevoke)]
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> DeleteContributor(string app, string id) public async Task<IActionResult> DeleteContributor(string app, string id)
{ {
await CommandBus.PublishAsync(new RemoveContributor { ContributorId = id }); var command = new RemoveContributor { ContributorId = id };
var context = await CommandBus.PublishAsync(command);
return NoContent(); var response = ContributorsDto.FromApp(context.Result<IAppEntity>(), appPlansProvider, this, false);
return Ok(response);
} }
} }
} }

30
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorAssignedDto.cs

@ -1,30 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public sealed class ContributorAssignedDto
{
/// <summary>
/// The id of the user that has been assigned as contributor.
/// </summary>
[Required]
public string ContributorId { get; set; }
/// <summary>
/// Indicates if the user was created.
/// </summary>
public bool IsCreated { get; set; }
public static ContributorAssignedDto FromId(string id, bool isCreated)
{
return new ContributorAssignedDto { ContributorId = id, IsCreated = isCreated };
}
}
}

16
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs

@ -5,11 +5,13 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models namespace Squidex.Areas.Api.Controllers.Apps.Models
{ {
public sealed class ContributorDto public sealed class ContributorDto : Resource
{ {
/// <summary> /// <summary>
/// The id of the user that contributes to the app. /// The id of the user that contributes to the app.
@ -21,5 +23,17 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// The role of the contributor. /// The role of the contributor.
/// </summary> /// </summary>
public string Role { get; set; } public string Role { get; set; }
public static ContributorDto FromIdAndRole(string id, string role, ApiController controller, string app)
{
var result = new ContributorDto { ContributorId = id, Role = role };
return CreateLinks(result, controller, app);
}
private static ContributorDto CreateLinks(ContributorDto result, ApiController controller, string app)
{
return result;
}
} }
} }

36
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs

@ -6,13 +6,15 @@
// ========================================================================== // ==========================================================================
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models namespace Squidex.Areas.Api.Controllers.Apps.Models
{ {
public sealed class ContributorsDto public sealed class ContributorsDto : Resource
{ {
/// <summary> /// <summary>
/// The contributors. /// The contributors.
@ -25,13 +27,37 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// </summary> /// </summary>
public int MaxContributors { get; set; } public int MaxContributors { get; set; }
public static ContributorsDto FromApp(IAppEntity app, IAppPlansProvider plans) /// <summary>
/// The metadata.
/// </summary>
[JsonProperty("_meta")]
public ContributorsMetadata Metadata { get; set; }
public static ContributorsDto FromApp(IAppEntity app, IAppPlansProvider plans, ApiController controller, bool isInvited)
{ {
var plan = plans.GetPlanForApp(app); var contributors = app.Contributors.ToArray(x => ContributorDto.FromIdAndRole(x.Key, x.Value, controller, app.Name));
var result = new ContributorsDto
{
Contributors = contributors,
};
var contributors = app.Contributors.ToArray(x => new ContributorDto { ContributorId = x.Key, Role = x.Value }); if (isInvited)
{
result.Metadata = new ContributorsMetadata
{
IsInvited = isInvited.ToString()
};
}
return new ContributorsDto { Contributors = contributors, MaxContributors = plan.MaxContributors }; result.MaxContributors = plans.GetPlanForApp(app).MaxContributors;
return CreateLinks(result, controller, app.Name);
}
private static ContributorsDto CreateLinks(ContributorsDto result, ApiController controller, string app)
{
return result;
} }
} }
} }

17
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsMetadata.cs

@ -0,0 +1,17 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public sealed class ContributorsMetadata
{
/// <summary>
/// Indicates whether the user has been invited.
/// </summary>
public string IsInvited { get; set; }
}
}

7
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs

@ -112,8 +112,11 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
/// </summary> /// </summary>
public long Version { get; set; } public long Version { get; set; }
/// <summary>
/// The metadata.
/// </summary>
[JsonProperty("_meta")] [JsonProperty("_meta")]
public AssetMetadata Meta { get; set; } public AssetMetadata Metadata { get; set; }
public static AssetDto FromAsset(IAssetEntity asset, ApiController controller, string app, bool isDuplicate = false) public static AssetDto FromAsset(IAssetEntity asset, ApiController controller, string app, bool isDuplicate = false)
{ {
@ -121,7 +124,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
if (isDuplicate) if (isDuplicate)
{ {
response.Meta = new AssetMetadata { IsDuplicate = "true" }; response.Metadata = new AssetMetadata { IsDuplicate = "true" };
} }
return CreateLinks(response, controller, app); return CreateLinks(response, controller, app);

15
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -23,21 +23,24 @@
<ng-container *ngIf="contributorsState.contributors | async; let contributors"> <ng-container *ngIf="contributorsState.contributors | async; let contributors">
<table class="table table-items table-fixed" *ngIf="rolesState.roles | async; let roles"> <table class="table table-items table-fixed" *ngIf="rolesState.roles | async; let roles">
<tbody *ngFor="let contributorInfo of contributors; trackBy: trackByContributor"> <tbody *ngFor="let contributor of contributors; trackBy: trackByContributor">
<tr> <tr>
<td class="cell-user"> <td class="cell-user">
<img class="user-picture" title="{{contributorInfo.contributor.contributorId | sqxUserName}}" [attr.src]="contributorInfo.contributor.contributorId | sqxUserPicture" /> <img class="user-picture" title="{{contributor.contributorId | sqxUserName}}" [attr.src]="contributor.contributorId | sqxUserPicture" />
</td> </td>
<td class="cell-auto"> <td class="cell-auto">
<span class="user-name table-cell">{{contributorInfo.contributor.contributorId | sqxUserName}}</span> <span class="user-name table-cell">{{contributor.contributorId | sqxUserName}}</span>
</td> </td>
<td class="cell-time"> <td class="cell-time">
<select class="form-control" [ngModel]="contributorInfo.contributor.role" (ngModelChange)="changeRole(contributorInfo.contributor, $event)" [disabled]="contributorInfo.isCurrentUser"> <select class="form-control"
[ngModel]="contributor.role"
(ngModelChange)="changeRole(contributor, $event)"
[disabled]="contributor | sqxHasNoLink:'update'">
<option *ngFor="let role of roles" [ngValue]="role.name">{{role.name}}</option> <option *ngFor="let role of roles" [ngValue]="role.name">{{role.name}}</option>
</select> </select>
</td> </td>
<td class="cell-actions"> <td class="cell-actions">
<button *ngIf="!contributorInfo.isCurrentUser" type="button" class="btn btn-text-danger" (click)="remove(contributorInfo.contributor)"> <button *ngIf="!isCurrentUser" type="button" class="btn btn-text-danger" [disabled]="contributor | sqxHasNoLink:'delete'" (click)="remove(contributor)">
<i class="icon-bin2"></i> <i class="icon-bin2"></i>
</button> </button>
</td> </td>
@ -47,7 +50,7 @@
</table> </table>
<ng-container> <ng-container>
<div class="table-items-footer" *ngIf="(contributorsState.isMaxReached | async) === false"> <div class="table-items-footer" *ngIf="(contributorsState.isMaxReached | async) === false && contributorsState.links | async | sqxHasLink:'create'">
<form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()"> <form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()">
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col"> <div class="col">

6
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts

@ -37,7 +37,7 @@ export class UsersDataSource implements AutocompleteSource {
const results: any[] = []; const results: any[] = [];
for (let user of users) { for (let user of users) {
if (!contributors!.find(t => t.contributor.contributorId === user.id)) { if (!contributors!.find(t => t.contributorId === user.id)) {
results.push(user); results.push(user);
} }
} }
@ -110,7 +110,7 @@ export class ContributorsPageComponent implements OnInit {
} }
} }
public trackByContributor(index: number, contributorInfo: { contributor: ContributorDto }) { public trackByContributor(index: number, contributor: ContributorDto) {
return contributorInfo.contributor.contributorId; return contributor.contributorId;
} }
} }

101
src/Squidex/app/shared/services/contributors.service.spec.ts

@ -11,11 +11,13 @@ import { inject, TestBed } from '@angular/core/testing';
import { import {
AnalyticsService, AnalyticsService,
ApiUrlConfig, ApiUrlConfig,
ContributorAssignedDto,
ContributorDto, ContributorDto,
ContributorsDto, ContributorsDto,
ContributorsPayload,
ContributorsService, ContributorsService,
Version Resource,
Version,
withLinks
} from '@app/shared/internal'; } from '@app/shared/internal';
describe('ContributorsService', () => { describe('ContributorsService', () => {
@ -52,34 +54,13 @@ describe('ContributorsService', () => {
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toBeNull();
req.flush({ req.flush(contributorsResponse(1, 2, 3), {
contributors: [
{
contributorId: '123',
role: 'Owner'
},
{
contributorId: '456',
role: 'Owner'
}
],
maxContributors: 100
}, {
headers: { headers: {
etag: '2' etag: '2'
} }
}); });
expect(contributors!).toEqual({ expect(contributors!).toEqual({ payload: createContributors(1, 2, 3), version: new Version('2') });
payload: {
contributors: [
new ContributorDto('123', 'Owner'),
new ContributorDto('456', 'Owner')
],
maxContributors: 100
},
version: new Version('2')
});
})); }));
it('should make post request to assign contributor', it('should make post request to assign contributor',
@ -87,10 +68,10 @@ describe('ContributorsService', () => {
const dto = { contributorId: '123', role: 'Owner' }; const dto = { contributorId: '123', role: 'Owner' };
let contributorAssignedDto: ContributorAssignedDto; let contributors: ContributorsDto;
contributorsService.postContributor('my-app', dto, version).subscribe(result => { contributorsService.postContributor('my-app', dto, version).subscribe(result => {
contributorAssignedDto = result.payload; contributors = result;
}); });
const req = httpMock.expectOne('http://service/p/api/apps/my-app/contributors'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/contributors');
@ -98,21 +79,77 @@ describe('ContributorsService', () => {
expect(req.request.method).toEqual('POST'); expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toEqual(version.value); expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({ contributorId: '123', isCreated: true }); req.flush(contributorsResponse(1, 2, 3), {
headers: {
etag: '2'
}
});
expect(contributorAssignedDto!.contributorId).toEqual('123'); expect(contributors!).toEqual({ payload: createContributors(1, 2, 3), version: new Version('2') });
})); }));
it('should make delete request to remove contributor', it('should make delete request to remove contributor',
inject([ContributorsService, HttpTestingController], (contributorsService: ContributorsService, httpMock: HttpTestingController) => { inject([ContributorsService, HttpTestingController], (contributorsService: ContributorsService, httpMock: HttpTestingController) => {
contributorsService.deleteContributor('my-app', '123', version).subscribe(); const resource: Resource = {
_links: {
'delete': { method: 'DELETE', href: '/api/apps/my-app/contributors/123' }
}
};
let contributors: ContributorsDto;
contributorsService.deleteContributor('my-app', resource, version).subscribe(result => {
contributors = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/contributors/123'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/contributors/123');
expect(req.request.method).toEqual('DELETE'); expect(req.request.method).toEqual('DELETE');
expect(req.request.headers.get('If-Match')).toEqual(version.value); expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({}); req.flush(contributorsResponse(1, 2, 3), {
headers: {
etag: '2'
}
});
expect(contributors!).toEqual({ payload: createContributors(1, 2, 3), version: new Version('2') });
})); }));
});
function contributorsResponse(...ids: number[]) {
return {
contributors: ids.map(id => ({
contributorId: `id${id}`, role: id % 2 === 0 ? 'Owner' : 'Developer',
_links: {
update: { method: 'PUT', href: `/contributors/id${id}` }
}
})),
maxContributors: ids.length * 13,
_links: {
create: { method: 'POST', href: '/contributors' }
},
_meta: {
isInvited: 'true'
}
};
}
});
export function createContributors(...ids: number[]): ContributorsPayload {
return {
contributors: ids.map(id =>
withLinks(new ContributorDto(`id${id}`, id % 2 === 0 ? 'Owner' : 'Developer'), {
_links: {
update: { method: 'PUT', href: `/contributors/id${id}` }
}
})),
maxContributors: ids.length * 13,
_links: {
create: { method: 'POST', href: '/contributors' }
},
_meta: {
isInvited: 'true'
}
};
}

63
src/Squidex/app/shared/services/contributors.service.ts

@ -15,18 +15,27 @@ import {
ApiUrlConfig, ApiUrlConfig,
HTTP, HTTP,
mapVersioned, mapVersioned,
Metadata,
Model, Model,
pretifyError, pretifyError,
Resource,
ResourceLinks,
Version, Version,
Versioned Versioned,
withLinks
} from '@app/framework'; } from '@app/framework';
export type ContributorsDto = Versioned<{ export type ContributorsDto = Versioned<ContributorsPayload>;
export type ContributorsPayload = {
readonly _links?: ResourceLinks,
readonly _meta?: Metadata
readonly contributors: ContributorDto[], readonly contributors: ContributorDto[],
readonly maxContributors: number readonly maxContributors: number
}>; };
export class ContributorDto extends Model<AssignContributorDto> { export class ContributorDto extends Model<AssignContributorDto> {
public readonly _links: ResourceLinks = {};
constructor( constructor(
public readonly contributorId: string, public readonly contributorId: string,
public readonly role: string public readonly role: string
@ -35,11 +44,6 @@ export class ContributorDto extends Model<AssignContributorDto> {
} }
} }
export interface ContributorAssignedDto {
readonly contributorId: string;
readonly isCreated?: boolean;
}
export interface AssignContributorDto { export interface AssignContributorDto {
readonly contributorId: string; readonly contributorId: string;
readonly role: string; readonly role: string;
@ -62,25 +66,19 @@ export class ContributorsService {
mapVersioned(payload => { mapVersioned(payload => {
const body = payload.body; const body = payload.body;
const items: any[] = body.contributors; return parseContributors(body);
const contributors =
items.map(item =>
new ContributorDto(
item.contributorId,
item.role));
return { contributors, maxContributors: body.maxContributors };
}), }),
pretifyError('Failed to load contributors. Please reload.')); pretifyError('Failed to load contributors. Please reload.'));
} }
public postContributor(appName: string, dto: AssignContributorDto, version: Version): Observable<Versioned<ContributorAssignedDto>> { public postContributor(appName: string, dto: AssignContributorDto, version: Version): Observable<ContributorsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return HTTP.postVersioned(this.http, url, dto, version).pipe( return HTTP.postVersioned(this.http, url, dto, version).pipe(
mapVersioned(payload => { mapVersioned(payload => {
return <ContributorAssignedDto>payload.body; const body = payload.body;
return parseContributors(body);
}), }),
tap(() => { tap(() => {
this.analytics.trackEvent('Contributor', 'Configured', appName); this.analytics.trackEvent('Contributor', 'Configured', appName);
@ -88,13 +86,34 @@ export class ContributorsService {
pretifyError('Failed to add contributors. Please reload.')); pretifyError('Failed to add contributors. Please reload.'));
} }
public deleteContributor(appName: string, contributorId: string, version: Version): Observable<Versioned<any>> { public deleteContributor(appName: string, resource: Resource, version: Version): Observable<ContributorsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors/${contributorId}`); const link = resource._links['delete'];
const url = this.apiUrl.buildUrl(link.href);
return HTTP.deleteVersioned(this.http, url, version).pipe( return HTTP.requestVersioned(this.http, link.method, url, version).pipe(
mapVersioned(payload => {
const body = payload.body;
return parseContributors(body);
}),
tap(() => { tap(() => {
this.analytics.trackEvent('Contributor', 'Deleted', appName); this.analytics.trackEvent('Contributor', 'Deleted', appName);
}), }),
pretifyError('Failed to delete contributors. Please reload.')); pretifyError('Failed to delete contributors. Please reload.'));
} }
}
function parseContributors(body: any) {
const items: any[] = body.contributors;
const contributors =
items.map(item =>
withLinks(
new ContributorDto(
item.contributorId,
item.role),
item));
return withLinks({ contributors, maxContributors: body.maxContributors, _links: {} }, body);
} }

76
src/Squidex/app/shared/state/contributors.state.spec.ts

@ -9,29 +9,25 @@ import { of } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq'; import { IMock, It, Mock, Times } from 'typemoq';
import { import {
ContributorDto,
ContributorsService, ContributorsService,
ContributorsState, ContributorsState,
DialogService, DialogService,
versioned versioned
} from '@app/shared/internal'; } from '@app/shared/internal';
import { createContributors } from '../services/contributors.service.spec';
import { TestValues } from './_test-helpers'; import { TestValues } from './_test-helpers';
describe('ContributorsState', () => { describe('ContributorsState', () => {
const { const {
app, app,
appsState, appsState,
authService,
modifier,
newVersion, newVersion,
version version
} = TestValues; } = TestValues;
const oldContributors = [ const oldContributors = createContributors(1, 2, 3);
new ContributorDto('id1', 'Developer'),
new ContributorDto(modifier, 'Developer')
];
let dialogs: IMock<DialogService>; let dialogs: IMock<DialogService>;
let contributorsService: IMock<ContributorsService>; let contributorsService: IMock<ContributorsService>;
@ -41,7 +37,7 @@ describe('ContributorsState', () => {
dialogs = Mock.ofType<DialogService>(); dialogs = Mock.ofType<DialogService>();
contributorsService = Mock.ofType<ContributorsService>(); contributorsService = Mock.ofType<ContributorsService>();
contributorsState = new ContributorsState(contributorsService.object, appsState.object, authService.object, dialogs.object); contributorsState = new ContributorsState(contributorsService.object, appsState.object, dialogs.object);
}); });
afterEach(() => { afterEach(() => {
@ -51,17 +47,14 @@ describe('ContributorsState', () => {
describe('Loading', () => { describe('Loading', () => {
it('should load contributors', () => { it('should load contributors', () => {
contributorsService.setup(x => x.getContributors(app)) contributorsService.setup(x => x.getContributors(app))
.returns(() => of(versioned(version, { contributors: oldContributors, maxContributors: 3 }))).verifiable(); .returns(() => of(versioned(version, oldContributors))).verifiable();
contributorsState.load().subscribe(); contributorsState.load().subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([ expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.contributors);
{ isCurrentUser: false, contributor: oldContributors[0] },
{ isCurrentUser: true, contributor: oldContributors[1] }
]);
expect(contributorsState.snapshot.isMaxReached).toBeFalsy(); expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.isLoaded).toBeTruthy(); expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(3); expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors);
expect(contributorsState.snapshot.version).toEqual(version); expect(contributorsState.snapshot.version).toEqual(version);
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never()); dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
@ -69,7 +62,7 @@ describe('ContributorsState', () => {
it('should show notification on load when reload is true', () => { it('should show notification on load when reload is true', () => {
contributorsService.setup(x => x.getContributors(app)) contributorsService.setup(x => x.getContributors(app))
.returns(() => of(versioned(version, { contributors: oldContributors, maxContributors: 3 }))).verifiable(); .returns(() => of(versioned(version, oldContributors))).verifiable();
contributorsState.load(true).subscribe(); contributorsState.load(true).subscribe();
@ -82,61 +75,36 @@ describe('ContributorsState', () => {
describe('Updates', () => { describe('Updates', () => {
beforeEach(() => { beforeEach(() => {
contributorsService.setup(x => x.getContributors(app)) contributorsService.setup(x => x.getContributors(app))
.returns(() => of(versioned(version, { contributors: oldContributors, maxContributors: 3 }))).verifiable(); .returns(() => of(versioned(version, oldContributors))).verifiable();
contributorsState.load().subscribe(); contributorsState.load().subscribe();
}); });
it('should add contributor to snapshot when assigned', () => { it('should update contributors when user assigned', () => {
const newContributor = new ContributorDto('id3', 'Developer'); const updated = createContributors(1, 2, 3);
const request = { contributorId: 'mail2stehle@gmail.com', role: newContributor.role }; const request = { contributorId: 'mail2stehle@gmail.com', role: 'Developer' };
const response = { contributorId: newContributor.contributorId, isCreated: true };
contributorsService.setup(x => x.postContributor(app, request, version)) contributorsService.setup(x => x.postContributor(app, request, version))
.returns(() => of(versioned(newVersion, response))).verifiable(); .returns(() => of(versioned(newVersion, updated))).verifiable();
contributorsState.assign(request).subscribe(); contributorsState.assign(request).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([ expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.contributors);
{ isCurrentUser: false, contributor: oldContributors[0] }, expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors);
{ isCurrentUser: true, contributor: oldContributors[1] },
{ isCurrentUser: false, contributor: newContributor }
]);
expect(contributorsState.snapshot.isMaxReached).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion); expect(contributorsState.snapshot.version).toEqual(newVersion);
}); });
it('should update contributor in snapshot when assigned and already added', () => { it('should update contributors when contribution revoked', () => {
const newContributor = new ContributorDto(modifier, 'Owner'); const updated = createContributors(1, 2, 3);
const request = { ...newContributor };
const response = { contributorId: newContributor.contributorId, isCreated: true };
contributorsService.setup(x => x.postContributor(app, request, version))
.returns(() => of(versioned(newVersion, response))).verifiable();
contributorsState.assign(request).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([
{ isCurrentUser: false, contributor: oldContributors[0] },
{ isCurrentUser: true, contributor: newContributor }
]);
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion);
});
it('should remove contributor from snapshot when revoked', () => { contributorsService.setup(x => x.deleteContributor(app, oldContributors.contributors[0], version))
contributorsService.setup(x => x.deleteContributor(app, oldContributors[0].contributorId, version)) .returns(() => of(versioned(newVersion, updated))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
contributorsState.revoke(oldContributors[0]).subscribe(); contributorsState.revoke(oldContributors.contributors[0]).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([ expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.contributors);
{ isCurrentUser: true, contributor: oldContributors[1] } expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors);
]);
expect(contributorsState.snapshot.version).toEqual(newVersion); expect(contributorsState.snapshot.version).toEqual(newVersion);
}); });
}); });

70
src/Squidex/app/shared/state/contributors.state.ts

@ -13,6 +13,7 @@ import {
DialogService, DialogService,
ErrorDto, ErrorDto,
ImmutableArray, ImmutableArray,
ResourceLinks,
shareMapSubscribed, shareMapSubscribed,
shareSubscribed, shareSubscribed,
State, State,
@ -23,20 +24,12 @@ import {
import { import {
AssignContributorDto, AssignContributorDto,
ContributorDto, ContributorDto,
ContributorsPayload,
ContributorsService ContributorsService
} from './../services/contributors.service'; } from './../services/contributors.service';
import { AuthService } from './../services/auth.service';
import { AppsState } from './apps.state'; import { AppsState } from './apps.state';
interface SnapshotContributor {
// The contributor.
contributor: ContributorDto;
// Indicates if the contributor is the ucrrent user.
isCurrentUser: boolean;
}
interface Snapshot { interface Snapshot {
// All loaded contributors. // All loaded contributors.
contributors: ContributorsList; contributors: ContributorsList;
@ -52,9 +45,12 @@ interface Snapshot {
// The app version. // The app version.
version: Version; version: Version;
// The links.
links: ResourceLinks;
} }
type ContributorsList = ImmutableArray<SnapshotContributor>; type ContributorsList = ImmutableArray<ContributorDto>;
@Injectable() @Injectable()
export class ContributorsState extends State<Snapshot> { export class ContributorsState extends State<Snapshot> {
@ -74,13 +70,16 @@ export class ContributorsState extends State<Snapshot> {
this.changes.pipe(map(x => x.maxContributors), this.changes.pipe(map(x => x.maxContributors),
distinctUntilChanged()); distinctUntilChanged());
public links =
this.changes.pipe(map(x => x.links),
distinctUntilChanged());
constructor( constructor(
private readonly contributorsService: ContributorsService, private readonly contributorsService: ContributorsService,
private readonly appsState: AppsState, private readonly appsState: AppsState,
private readonly authState: AuthService,
private readonly dialogs: DialogService private readonly dialogs: DialogService
) { ) {
super({ contributors: ImmutableArray.empty(), version: new Version(''), maxContributors: -1 }); super({ contributors: ImmutableArray.empty(), version: new Version(''), maxContributors: -1, links: {} });
} }
public load(isReload = false): Observable<any> { public load(isReload = false): Observable<any> {
@ -94,19 +93,15 @@ export class ContributorsState extends State<Snapshot> {
this.dialogs.notifyInfo('Contributors reloaded.'); this.dialogs.notifyInfo('Contributors reloaded.');
} }
const contributors = ImmutableArray.of(payload.contributors.map(x => this.createContributor(x))); this.replaceContributors(version, payload);
this.replaceContributors(contributors, version, payload.maxContributors);
}), }),
shareSubscribed(this.dialogs)); shareSubscribed(this.dialogs));
} }
public revoke(contributor: ContributorDto): Observable<any> { public revoke(contributor: ContributorDto): Observable<any> {
return this.contributorsService.deleteContributor(this.appName, contributor.contributorId, this.version).pipe( return this.contributorsService.deleteContributor(this.appName, contributor, this.version).pipe(
tap(({ version }) => { tap(({ version, payload }) => {
const contributors = this.snapshot.contributors.filter(x => x.contributor.contributorId !== contributor.contributorId); this.replaceContributors(version, payload);
this.replaceContributors(contributors, version);
}), }),
shareSubscribed(this.dialogs)); shareSubscribed(this.dialogs));
} }
@ -121,32 +116,21 @@ export class ContributorsState extends State<Snapshot> {
} }
}), }),
tap(({ version, payload }) => { tap(({ version, payload }) => {
const contributors = this.updateContributors(payload.contributorId, request.role); this.replaceContributors(version, payload);
this.replaceContributors(contributors, version);
}), }),
shareMapSubscribed(this.dialogs, x => x.payload.isCreated)); shareMapSubscribed(this.dialogs, x => x.payload._meta && x.payload._meta['isInvited'] === '1'));
} }
private updateContributors(id: string, role: string) { private replaceContributors(version: Version, payload: ContributorsPayload) {
const contributor = new ContributorDto(id, role);
const contributors = this.snapshot.contributors;
if (contributors.find(x => x.contributor.contributorId === id)) {
return contributors.map(x => x.contributor.contributorId === id ? this.createContributor(contributor) : x);
} else {
return contributors.push(this.createContributor(contributor));
}
}
private replaceContributors(contributors: ImmutableArray<SnapshotContributor>, version: Version, maxContributors?: number) {
this.next(s => { this.next(s => {
maxContributors = maxContributors || s.maxContributors; const maxContributors = payload.maxContributors || s.maxContributors;
const isLoaded = true; const isLoaded = true;
const isMaxReached = maxContributors > 0 && maxContributors <= contributors.length; const isMaxReached = maxContributors > 0 && maxContributors <= payload.contributors.length;
const contributors = ImmutableArray.of(payload.contributors);
return { ...s, contributors, maxContributors, isLoaded, isMaxReached, version }; return { ...s, contributors, maxContributors, isLoaded, isMaxReached, version: version, links: payload._links };
}); });
} }
@ -154,15 +138,7 @@ export class ContributorsState extends State<Snapshot> {
return this.appsState.appName; return this.appsState.appName;
} }
private get userId() {
return this.authState.user!.id;
}
private get version() { private get version() {
return this.snapshot.version; return this.snapshot.version;
} }
private createContributor(contributor: ContributorDto): SnapshotContributor {
return { contributor, isCurrentUser: contributor.contributorId === this.userId };
}
} }

32
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs

@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(EntityCreatedResult.Create(Id, 4)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(AppName, sut.Snapshot.Name); Assert.Equal(AppName, sut.Snapshot.Name);
@ -187,7 +187,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 5)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Role.Editor, sut.Snapshot.Contributors[contributorId]); Assert.Equal(Role.Editor, sut.Snapshot.Contributors[contributorId]);
@ -207,7 +207,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 6)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Role.Owner, sut.Snapshot.Contributors[contributorId]); Assert.Equal(Role.Owner, sut.Snapshot.Contributors[contributorId]);
@ -227,7 +227,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(sut.Snapshot.Contributors.ContainsKey(contributorId)); Assert.False(sut.Snapshot.Contributors.ContainsKey(contributorId));
@ -246,7 +246,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.Clients.ContainsKey(clientId)); Assert.True(sut.Snapshot.Clients.ContainsKey(clientId));
@ -266,7 +266,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(sut.Snapshot.Clients.ContainsKey(clientId)); Assert.False(sut.Snapshot.Clients.ContainsKey(clientId));
@ -286,7 +286,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(7)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(clientNewName, sut.Snapshot.Clients[clientId].Name); Assert.Equal(clientNewName, sut.Snapshot.Clients[clientId].Name);
@ -306,7 +306,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE)); Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE));
@ -326,7 +326,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(sut.Snapshot.LanguagesConfig.Contains(Language.DE)); Assert.False(sut.Snapshot.LanguagesConfig.Contains(Language.DE));
@ -346,7 +346,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE)); Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE));
@ -365,7 +365,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(5, sut.Snapshot.Roles.Count); Assert.Equal(5, sut.Snapshot.Roles.Count);
@ -385,7 +385,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(4, sut.Snapshot.Roles.Count); Assert.Equal(4, sut.Snapshot.Roles.Count);
@ -405,7 +405,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6)); result.ShouldBeEquivalent(sut.Snapshot);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
@ -422,7 +422,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(5)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(initialPatterns.Count + 1, sut.Snapshot.Patterns.Count); Assert.Equal(initialPatterns.Count + 1, sut.Snapshot.Patterns.Count);
@ -442,7 +442,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6)); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(initialPatterns.Count, sut.Snapshot.Patterns.Count); Assert.Equal(initialPatterns.Count, sut.Snapshot.Patterns.Count);
@ -462,7 +462,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(6)); result.ShouldBeEquivalent(sut.Snapshot);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(

8
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs

@ -34,13 +34,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true)) A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true))
.Returns(true); .Returns(true);
var result = EntityCreatedResult.Create("13", 13L); var result = A.Fake<IAppEntity>();
context.Complete(result); context.Complete(result);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.Same(context.Result<InvitedResult>().Id, result); Assert.Same(context.Result<InvitedResult>().App, result);
A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true)) A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true))
.MustHaveHappened(); .MustHaveHappened();
@ -55,13 +55,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true)) A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true))
.Returns(false); .Returns(false);
var result = EntityCreatedResult.Create("13", 13L); var result = A.Fake<IAppEntity>();
context.Complete(result); context.Complete(result);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.Same(context.Result<EntityCreatedResult<string>>(), result); Assert.Same(context.Result<IAppEntity>(), result);
A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true)) A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true))
.MustHaveHappened(); .MustHaveHappened();

Loading…
Cancel
Save