Browse Source

Fix contributors page.

pull/282/head
Sebastian Stehle 8 years ago
parent
commit
ea789b8aff
  1. 7
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs
  2. 18
      src/Squidex/app/features/administration/state/users.state.spec.ts
  3. 2
      src/Squidex/app/features/administration/state/users.state.ts
  4. 20
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  5. 93
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  6. 5
      src/Squidex/app/features/settings/pages/more/more-page.component.ts
  7. 1
      src/Squidex/app/shared/internal.ts
  8. 2
      src/Squidex/app/shared/module.ts
  9. 9
      src/Squidex/app/shared/state/apps.state.spec.ts
  10. 7
      src/Squidex/app/shared/state/apps.state.ts
  11. 5
      src/Squidex/app/shared/state/clients.state.spec.ts
  12. 116
      src/Squidex/app/shared/state/contributors.state.spec.ts
  13. 137
      src/Squidex/app/shared/state/contributors.state.ts
  14. 8
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs

7
src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Apps;
@ -44,7 +45,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
command.ContributorId = user.Id;
if (contributors.TryGetValue(command.ContributorId, out var existing))
if (string.Equals(command.ContributorId, command.Actor.Identifier, StringComparison.OrdinalIgnoreCase))
{
error(new ValidationError("You cannot change your own permission."));
}
else if (contributors.TryGetValue(command.ContributorId, out var existing))
{
if (existing == command.Permission)
{

18
src/Squidex/app/features/administration/state/users.state.spec.ts

@ -51,7 +51,7 @@ describe('UsersState', () => {
});
it('should load users', () => {
expect(usersState.snapshot.users.values).toEqual([{ isCurrentUser: false, user: oldUsers[0] }, { isCurrentUser: true, user: oldUsers[1] }]);
expect(usersState.snapshot.users.values).toEqual(oldUsers.map(x => u(x)));
expect(usersState.snapshot.usersPager.numberOfItems).toEqual(200);
usersService.verifyAll();
@ -76,7 +76,7 @@ describe('UsersState', () => {
usersState.load().subscribe();
expect(usersState.snapshot.selectedUser).toEqual({ isCurrentUser: false, user: newUsers[0] });
expect(usersState.snapshot.selectedUser).toEqual(u(newUsers[0]));
});
it('should mark as current user when selected user equals to profile', () => {
@ -87,7 +87,7 @@ describe('UsersState', () => {
});
expect(selectedUser!).toEqual(oldUsers[1]);
expect(usersState.snapshot.selectedUser!).toEqual({ isCurrentUser: true, user: oldUsers[1] });
expect(usersState.snapshot.selectedUser).toEqual(u(oldUsers[1]));
});
it('should not load user when already loaded', () => {
@ -98,7 +98,7 @@ describe('UsersState', () => {
});
expect(selectedUser!).toEqual(oldUsers[0]);
expect(usersState.snapshot.selectedUser).toEqual({ isCurrentUser: false, user: oldUsers[0] });
expect(usersState.snapshot.selectedUser).toEqual(u(oldUsers[0]));
usersService.verify(x => x.getUser(It.isAnyString()), Times.never());
});
@ -114,7 +114,7 @@ describe('UsersState', () => {
});
expect(selectedUser!).toEqual(newUser);
expect(usersState.snapshot.selectedUser).toEqual({ isCurrentUser: false, user: newUser });
expect(usersState.snapshot.selectedUser).toEqual(u(newUser));
usersService.verify(x => x.getUser('id3'), Times.once());
});
@ -166,7 +166,7 @@ describe('UsersState', () => {
usersState.select('id2').subscribe();
usersState.unlock(oldUsers[1]).subscribe();
const user_1 = usersState.snapshot.users.at(0);
const user_1 = usersState.snapshot.users.at(1);
expect(user_1.user.isLocked).toBeFalsy();
expect(user_1).toBe(usersState.snapshot.selectedUser);
@ -196,7 +196,7 @@ describe('UsersState', () => {
usersState.create(request).subscribe();
expect(usersState.snapshot.users.at(0).user).toBe(newUser);
expect(usersState.snapshot.users.values).toEqual([u(newUser), ...oldUsers.map(x => u(x))]);
expect(usersState.snapshot.usersPager.numberOfItems).toBe(201);
});
@ -221,4 +221,8 @@ describe('UsersState', () => {
usersService.verify(x => x.getUsers(10, 0, 'my-query'), Times.once());
});
function u(user: UserDto) {
return { user, isCurrentUser: user.id === 'id2' };
}
});

2
src/Squidex/app/features/administration/state/users.state.ts

@ -206,7 +206,7 @@ export class UsersState extends State<Snapshot> {
private replaceUser(userDto: UserDto) {
return this.next(s => {
const user = this.createUser(userDto);
const users = s.users.replaceBy('id', user);
const users = s.users.map(u => u.user.id === userDto.id ? user : u);
const selectedUser = s.selectedUser && s.selectedUser.user.id === userDto.id ? user : s.selectedUser;

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

@ -6,27 +6,30 @@
</ng-container>
<ng-container content>
<ng-container *ngIf="contributorsState.maxContributors | async; let maxContributors">
<div class="panel-alert panel-alert-success" *ngIf="maxContributors > 0">
Your plan allows up to {{maxContributors}} contributors.
</div>
</ng-container>
<ng-container *ngIf="contributorsState.contributors | async; let contributors">
<table class="table table-items table-fixed">
<tbody>
<ng-template ngFor let-contributor [ngForOf]="appContributors?.contributors">
<ng-template ngFor let-contributorInfo [ngForOf]="contributors">
<tr>
<td class="cell-user">
<img class="user-picture" [attr.title]="contributor.contributorId | sqxUserName" [attr.src]="contributor.contributorId | sqxUserPicture" />
<img class="user-picture" [attr.title]="contributorInfo.contributor.contributorId | sqxUserName" [attr.src]="contributorInfo.contributor.contributorId | sqxUserPicture" />
</td>
<td class="cell-auto">
<span class="user-name table-cell">{{contributor.contributorId | sqxUserName}}</span>
<span class="user-name table-cell">{{contributorInfo.contributor.contributorId | sqxUserName}}</span>
</td>
<td class="cell-time">
<select class="form-control" [ngModel]="contributor.permission" (ngModelChange)="changePermission(contributor, $event)" [disabled]="authState.user?.id === contributor.contributorId">
<select class="form-control" [ngModel]="contributorInfo.contributor.permission" (ngModelChange)="changePermission(contributorInfo.contributor, $event)" [disabled]="contributorInfo.isCurrentUser">
<option *ngFor="let permission of usersPermissions" [ngValue]="permission">{{permission}}</option>
</select>
</td>
<td class="cell-actions">
<button *ngIf="authState.user?.id !== contributor.contributorId" type="button" class="btn btn-link btn-danger" (click)="removeContributor(contributor)">
<button *ngIf="!contributorInfo.isCurrentUser" type="button" class="btn btn-link btn-danger" (click)="removeContributor(contributorInfo.contributor)">
<i class="icon-bin2"></i>
</button>
</td>
@ -35,9 +38,10 @@
</ng-template>
</tbody>
</table>
</ng-container>
<div class="table-items-footer" *ngIf="appContributors">
<form [formGroup]="addContributorForm" (ngSubmit)="assignContributor()">
<div class="table-items-footer" *ngIf="(contributorsState.isMaxReached | async) === false">
<form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()">
<div class="row no-gutters">
<div class="col">
<sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user" displayProperty="displayName">
@ -51,7 +55,7 @@
</sqx-autocomplete>
</div>
<div class="col col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="!canAddContributor">Add Contributor</button>
<button type="submit" class="btn btn-success" [disabled]="assignContributorForm.noUser | async">Add Contributor</button>
</div>
</div>
</form>

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

@ -6,17 +6,15 @@
*/
import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs';
import {
AppContributorDto,
AppContributorsDto,
AppContributorsService,
AppsState,
AuthService,
AssignContributorForm,
AutocompleteSource,
DialogService,
ContributorsState,
PublicUserDto,
UsersService
} from '@app/shared';
@ -30,11 +28,11 @@ export class UsersDataSource implements AutocompleteSource {
public find(query: string): Observable<any[]> {
return this.usersService.getUsers(query)
.map(users => {
.withLatestFrom(this.component.contributorsState.contributors, (users, contributors) => {
const results: any[] = [];
for (let user of users) {
if (!this.component.appContributors || !this.component.appContributors.contributors.find(t => t.contributorId === user.id)) {
if (!contributors.find(t => t.contributor.contributorId === user.id)) {
results.push(user);
}
}
@ -49,31 +47,14 @@ export class UsersDataSource implements AutocompleteSource {
templateUrl: './contributors-page.component.html'
})
export class ContributorsPageComponent implements OnInit {
public appContributors: AppContributorsDto;
public maxContributors = -1;
public usersDataSource: UsersDataSource;
public usersPermissions = [ 'Owner', 'Developer', 'Editor' ];
public get canAddContributor() {
return this.addContributorForm.valid && (this.maxContributors <= -1 || this.appContributors.contributors.length < this.maxContributors);
}
public addContributorForm =
this.formBuilder.group({
user: [null,
[
Validators.required
]
]
});
public assignContributorForm = new AssignContributorForm(this.formBuilder);
constructor(
public readonly appsState: AppsState,
public readonly authState: AuthService,
private readonly appContributorsService: AppContributorsService,
private readonly dialogs: DialogService,
public readonly contributorsState: ContributorsState,
private readonly formBuilder: FormBuilder,
usersService: UsersService
) {
@ -81,69 +62,35 @@ export class ContributorsPageComponent implements OnInit {
}
public ngOnInit() {
this.load();
}
public load() {
this.appContributorsService.getContributors(this.appsState.appName)
.subscribe(dto => {
this.updateContributorsFromDto(dto);
}, error => {
this.dialogs.notifyError(error);
});
this.contributorsState.load().onErrorResumeNext().subscribe();
}
public removeContributor(contributor: AppContributorDto) {
this.appContributorsService.deleteContributor(this.appsState.appName, contributor.contributorId, this.appContributors.version)
.subscribe(dto => {
this.updateContributors(this.appContributors.removeContributor(contributor, dto.version));
}, error => {
this.dialogs.notifyError(error);
});
this.contributorsState.revoke(contributor).onErrorResumeNext().subscribe();
}
public changePermission(contributor: AppContributorDto, permission: string) {
const requestDto = contributor.changePermission(permission);
this.appContributorsService.postContributor(this.appsState.appName, requestDto, this.appContributors.version)
.subscribe(dto => {
this.updateContributors(this.appContributors.updateContributor(contributor, dto.version));
}, error => {
this.dialogs.notifyError(error);
});
this.contributorsState.assign(new AppContributorDto(contributor.contributorId, permission)).onErrorResumeNext().subscribe();
}
public assignContributor() {
let value: any = this.addContributorForm.controls['user'].value;
const value = this.assignContributorForm.submit();
if (value) {
let user = value.user;
if (value instanceof PublicUserDto) {
value = value.id;
if (user instanceof PublicUserDto) {
user = user.id;
}
const requestDto = new AppContributorDto(value, 'Editor');
const requestDto = new AppContributorDto(user, 'Editor');
this.appContributorsService.postContributor(this.appsState.appName, requestDto, this.appContributors.version)
this.contributorsState.assign(requestDto)
.subscribe(dto => {
this.updateContributors(this.appContributors.addContributor(new AppContributorDto(dto.payload.contributorId, requestDto.permission), dto.version));
this.resetContributorForm();
this.assignContributorForm.submitCompleted();
}, error => {
this.dialogs.notifyError(error);
this.resetContributorForm();
this.assignContributorForm.submitFailed(error);
});
}
private resetContributorForm() {
this.addContributorForm.reset();
}
private updateContributorsFromDto(appContributors: AppContributorsDto) {
this.updateContributors(appContributors);
this.maxContributors = appContributors.maxContributors;
}
private updateContributors(appContributors: AppContributorsDto) {
this.appContributors = appContributors;
}
}

5
src/Squidex/app/features/settings/pages/more/more-page.component.ts

@ -8,7 +8,7 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AppsState, DialogService } from '@app/shared';
import { AppsState } from '@app/shared';
@Component({
selector: 'sqx-more-page',
@ -18,7 +18,6 @@ import { AppsState, DialogService } from '@app/shared';
export class MorePageComponent {
constructor(
public readonly appsState: AppsState,
private readonly dialogs: DialogService,
private readonly router: Router
) {
}
@ -27,8 +26,6 @@ export class MorePageComponent {
this.appsState.delete(this.appsState.appName)
.subscribe(() => {
this.router.navigate(['/app']);
}, error => {
this.dialogs.notifyError(error);
});
}
}

1
src/Squidex/app/shared/internal.ts

@ -41,6 +41,7 @@ export * from './services/users.service';
export * from './state/apps.state';
export * from './state/assets.state';
export * from './state/clients.state';
export * from './state/contributors.state';
export * from './state/patterns.state';
export * from './state/schemas.state';

2
src/Squidex/app/shared/module.ts

@ -34,6 +34,7 @@ import {
BackupsService,
ClientsState,
ContentsService,
ContributorsState,
FileIconPipe,
GeolocationEditorComponent,
GraphQlService,
@ -141,6 +142,7 @@ export class SqxSharedModule {
BackupsService,
ClientsState,
ContentsService,
ContributorsState,
GraphQlService,
HelpService,
HistoryService,

9
src/Squidex/app/shared/state/apps.state.spec.ts

@ -13,7 +13,8 @@ import {
AppsService,
AppsState,
CreateAppDto,
DateTime
DateTime,
DialogService
} from './../';
describe('AppsState', () => {
@ -23,19 +24,23 @@ describe('AppsState', () => {
new AppDto('id1', 'old-name1', 'Owner', now, now, 'Free', 'Plan'),
new AppDto('id2', 'old-name2', 'Owner', now, now, 'Free', 'Plan')
];
const newApp = new AppDto('id3', 'new-name', 'Owner', now, now, 'Free', 'Plan');
let dialogs: IMock<DialogService>;
let appsService: IMock<AppsService>;
let appsState: AppsState;
beforeEach(() => {
dialogs = Mock.ofType<DialogService>();
appsService = Mock.ofType<AppsService>();
appsService.setup(x => x.getApps())
.returns(() => Observable.of(oldApps))
.verifiable(Times.once());
appsState = new AppsState(appsService.object);
appsState = new AppsState(appsService.object, dialogs.object);
appsState.load().subscribe();
});

7
src/Squidex/app/shared/state/apps.state.ts

@ -14,6 +14,7 @@ import '@app/framework/utils/rxjs-extensions';
import {
DateTime,
DialogService,
Form,
ImmutableArray,
State,
@ -67,7 +68,8 @@ export class AppsState extends State<Snapshot> {
.distinctUntilChanged();
constructor(
private readonly appsService: AppsService
private readonly appsService: AppsService,
private readonly dialogs: DialogService
) {
super({ apps: ImmutableArray.empty(), selectedApp: null });
}
@ -116,6 +118,7 @@ export class AppsState extends State<Snapshot> {
return { ...s, apps, selectedApp };
});
});
})
.notify(this.dialogs);
}
}

5
src/Squidex/app/shared/state/clients.state.spec.ts

@ -14,11 +14,11 @@ import {
AppClientsDto,
AppClientsService,
ClientsState,
CreateAppClientDto,
DialogService,
UpdateAppClientDto,
Version,
Versioned,
CreateAppClientDto
Versioned
} from '@app/shared';
describe('ClientsState', () => {
@ -55,6 +55,7 @@ describe('ClientsState', () => {
it('should load clients', () => {
expect(clientsState.snapshot.clients.values).toEqual(oldClients);
expect(clientsState.snapshot.isLoaded).toBeTruthy();
expect(clientsState.snapshot.version).toEqual(version);
});

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

@ -0,0 +1,116 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Observable } from 'rxjs';
import { IMock, Mock } from 'typemoq';
import {
AppsState,
AppContributorDto,
AppContributorsDto,
AppContributorsService,
AuthService,
ContributorsState,
DialogService,
Version,
Versioned
} from '@app/shared';
describe('ContributorsState', () => {
const app = 'my-app';
const version = new Version('1');
const newVersion = new Version('2');
const oldContributors = [
new AppContributorDto('id1', 'Developer'),
new AppContributorDto('id2', 'Developer')
];
let dialogs: IMock<DialogService>;
let appsState: IMock<AppsState>;
let authService: IMock<AuthService>;
let contributorsService: IMock<AppContributorsService>;
let contributorsState: ContributorsState;
beforeEach(() => {
dialogs = Mock.ofType<DialogService>();
authService = Mock.ofType<AuthService>();
authService.setup(x => x.user)
.returns(() => <any>{ id: 'id2' });
appsState = Mock.ofType<AppsState>();
appsState.setup(x => x.appName)
.returns(() => app);
contributorsService = Mock.ofType<AppContributorsService>();
contributorsService.setup(x => x.getContributors(app))
.returns(() => Observable.of(new AppContributorsDto(oldContributors, 3, version)));
contributorsState = new ContributorsState(contributorsService.object, appsState.object, authService.object, dialogs.object);
contributorsState.load().subscribe();
});
it('should load contributors', () => {
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.map(x => c(x)));
expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(version);
});
it('should add contributor to snapshot', () => {
const newContributor = new AppContributorDto('id3', 'Developer');
const request = new AppContributorDto('mail2stehle@gmail.com', 'Developer');
contributorsService.setup(x => x.postContributor(app, request, version))
.returns(() => Observable.of(new Versioned<AppContributorDto>(newVersion, newContributor)));
contributorsState.assign(request).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([...oldContributors.map(x => c(x)), c(newContributor)]);
expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.isMaxReached).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion);
});
it('should update contributor in snapshot', () => {
const newContributor = new AppContributorDto('id2', 'Owner');
const request = new AppContributorDto('mail2stehle@gmail.com', 'Owner');
contributorsService.setup(x => x.postContributor(app, request, version))
.returns(() => Observable.of(new Versioned<AppContributorDto>(newVersion, newContributor)));
contributorsState.assign(request).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([c(oldContributors[0]), c(newContributor)]);
expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion);
});
it('should remove contributor from snapshot', () => {
contributorsService.setup(x => x.deleteContributor(app, oldContributors[0].contributorId, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {})));
contributorsState.revoke(oldContributors[0]).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual([c(oldContributors[1])]);
expect(contributorsState.snapshot.version).toEqual(newVersion);
});
function c(contributor: AppContributorDto) {
return { contributor, isCurrentUser: contributor.contributorId === 'id2' };
}
});

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

@ -0,0 +1,137 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import '@app/framework/utils/rxjs-extensions';
import {
DialogService,
ImmutableArray,
Form,
State,
Version
} from '@app/framework';
import { AuthService } from './../services/auth.service';
import { AppsState } from './apps.state';
import { AppContributorDto, AppContributorsService } from './../services/app-contributors.service';
export class AssignContributorForm extends Form<FormGroup> {
public hasNoUser =
this.form.controls['user'].valueChanges.startWith(null).map(x => !x);
constructor(formBuilder: FormBuilder) {
super(formBuilder.group({
user: [null,
[
Validators.required
]
]
}));
}
}
interface SnapshotContributor {
contributor: AppContributorDto;
isCurrentUser: boolean;
}
interface Snapshot {
contributors: ImmutableArray<SnapshotContributor>;
isLoaded: boolean;
isMaxReached: boolean;
maxContributors: number;
version: Version;
}
@Injectable()
export class ContributorsState extends State<Snapshot> {
public contributors =
this.changes.map(x => x.contributors);
public isLoaded =
this.changes.map(x => x.isLoaded);
public isMaxReached =
this.changes.map(x => x.isMaxReached);
public maxContributors =
this.changes.map(x => x.maxContributors);
constructor(
private readonly appContributorsService: AppContributorsService,
private readonly appsState: AppsState,
private readonly authState: AuthService,
private readonly dialogs: DialogService
) {
super({ contributors: ImmutableArray.empty(), version: new Version(''), isLoaded: false, isMaxReached: true, maxContributors: -1 });
}
public load(): Observable<any> {
return this.appContributorsService.getContributors(this.appName)
.do(dtos => {
const contributors = ImmutableArray.of(dtos.contributors.map(x => this.createContributor(x)));
this.replaceContributors(contributors, dtos.version, dtos.maxContributors);
})
.notify(this.dialogs);
}
public revoke(contributor: AppContributorDto): Observable<any> {
return this.appContributorsService.deleteContributor(this.appName, contributor.contributorId, this.snapshot.version)
.do(dto => {
const contributors = this.snapshot.contributors.filter(x => x.contributor.contributorId !== contributor.contributorId);
this.replaceContributors(contributors, dto.version);
})
.notify(this.dialogs);
}
public assign(request: AppContributorDto): Observable<any> {
return this.appContributorsService.postContributor(this.appName, request, this.snapshot.version)
.do(dto => {
const contributor = this.createContributor(new AppContributorDto(dto.payload.contributorId, request.permission));
let contributors = this.snapshot.contributors;
if (contributors.find(x => x.contributor.contributorId === dto.payload.contributorId)) {
contributors = contributors.map(c => c.contributor.contributorId === dto.payload.contributorId ? contributor : c);
} else {
contributors = contributors.push(contributor);
}
this.replaceContributors(contributors, dto.version);
})
.notify(this.dialogs);
}
private replaceContributors(contributors: ImmutableArray<SnapshotContributor>, version: Version, maxContributors?: number) {
this.next(s => {
maxContributors = maxContributors || s.maxContributors;
const isLoaded = true;
const isMaxReached = maxContributors > 0 && maxContributors <= contributors.length;
return { ...s, contributors, maxContributors, isLoaded, isMaxReached, version };
});
}
private get appName() {
return this.appsState.appName;
}
private createContributor(contributor: AppContributorDto): SnapshotContributor {
return { contributor, isCurrentUser: contributor.contributorId === this.authState.user!.id };
}
}

8
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs

@ -82,6 +82,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
return Assert.ThrowsAsync<ValidationException>(() => GuardAppContributors.CanAssign(contributors_0, command, users, appPlan));
}
[Fact]
public Task CanAssign_should_throw_exception_if_user_is_actor()
{
var command = new AssignContributor { ContributorId = "3", Permission = (AppContributorPermission)10, Actor = new RefToken("user", "3") };
return Assert.ThrowsAsync<ValidationException>(() => GuardAppContributors.CanAssign(contributors_0, command, users, appPlan));
}
[Fact]
public Task CanAssign_should_throw_exception_if_contributor_max_reached()
{

Loading…
Cancel
Save