Browse Source

Error handling improved

pull/1/head
Sebastian 10 years ago
parent
commit
d32a54cbcd
  1. 12
      src/Squidex.Write/Schemas/SchemaCommandHandler.cs
  2. 36
      src/Squidex.Write/Schemas/SchemaDomainObject.cs
  3. 40
      src/Squidex/Config/Identity/LazyClientStore.cs
  4. 27
      src/Squidex/Pipeline/RandomErrorAttribute.cs
  5. 2
      src/Squidex/Startup.cs
  6. 2
      src/Squidex/app/app.module.ts
  7. 2
      src/Squidex/app/components/internal/app/settings/clients-page.component.html
  8. 26
      src/Squidex/app/components/internal/app/settings/clients-page.component.ts
  9. 38
      src/Squidex/app/components/internal/app/settings/contributors-page.component.ts
  10. 2
      src/Squidex/app/components/internal/app/settings/languages-page.component.html
  11. 34
      src/Squidex/app/components/internal/app/settings/languages-page.component.ts
  12. 6
      src/Squidex/app/components/internal/internal-area.component.html
  13. 22
      src/Squidex/app/components/internal/internal-area.component.scss
  14. 43
      src/Squidex/app/components/internal/internal-area.component.ts
  15. 4
      src/Squidex/app/components/layout/app-form.component.html
  16. 1
      src/Squidex/app/framework/declarations.ts
  17. 1
      src/Squidex/app/framework/services/clipboard.service.spec.ts
  18. 41
      src/Squidex/app/framework/services/notification.service.spec.ts
  19. 44
      src/Squidex/app/framework/services/notification.service.ts
  20. 2
      src/Squidex/app/shared/services/app-clients.service.spec.ts
  21. 16
      src/Squidex/app/shared/services/app-clients.service.ts
  22. 2
      src/Squidex/app/shared/services/users-provider.service.ts
  23. 16
      tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs
  24. 358
      tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs

12
src/Squidex.Write/Schemas/SchemaCommandHandler.cs

@ -52,32 +52,32 @@ namespace Squidex.Write.Schemas
public Task On(DeleteSchema command, CommandContext context)
{
return UpdateAsync(command, s => s.Delete());
return UpdateAsync(command, s => s.Delete(command));
}
public Task On(DeleteField command, CommandContext context)
{
return UpdateAsync(command, s => s.DeleteField(command.FieldId));
return UpdateAsync(command, s => s.DeleteField(command));
}
public Task On(DisableField command, CommandContext context)
{
return UpdateAsync(command, s => s.DisableField(command.FieldId));
return UpdateAsync(command, s => s.DisableField(command));
}
public Task On(EnableField command, CommandContext context)
{
return UpdateAsync(command, s => s.EnableField(command.FieldId));
return UpdateAsync(command, s => s.EnableField(command));
}
public Task On(HideField command, CommandContext context)
{
return UpdateAsync(command, s => s.HideField(command.FieldId));
return UpdateAsync(command, s => s.HideField(command));
}
public Task On(ShowField command, CommandContext context)
{
return UpdateAsync(command, s => s.ShowField(command.FieldId));
return UpdateAsync(command, s => s.ShowField(command));
}
public Task On(UpdateSchema command, CommandContext context)

36
src/Squidex.Write/Schemas/SchemaDomainObject.cs

@ -116,7 +116,7 @@ namespace Squidex.Write.Schemas
public SchemaDomainObject UpdateField(UpdateField command)
{
Guard.Valid(command, nameof(command), () => $"Cannot update schema '{schema.Name} ({Id})'");
Guard.Valid(command, nameof(command), () => $"Cannot update schema '{Id}'");
VerifyCreatedAndNotDeleted();
@ -138,7 +138,7 @@ namespace Squidex.Write.Schemas
public SchemaDomainObject Update(UpdateSchema command)
{
Guard.Valid(command, nameof(command), () => $"Cannot update schema '{schema.Name} ({Id})'");
Guard.Valid(command, nameof(command), () => $"Cannot update schema '{Id}'");
VerifyCreatedAndNotDeleted();
@ -147,52 +147,62 @@ namespace Squidex.Write.Schemas
return this;
}
public SchemaDomainObject HideField(long fieldId)
public SchemaDomainObject HideField(HideField command)
{
Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldHidden { FieldId = fieldId });
RaiseEvent(new FieldHidden { FieldId = command.FieldId });
return this;
}
public SchemaDomainObject ShowField(long fieldId)
public SchemaDomainObject ShowField(ShowField command)
{
Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldShown { FieldId = fieldId });
RaiseEvent(new FieldShown { FieldId = command.FieldId });
return this;
}
public SchemaDomainObject DisableField(long fieldId)
public SchemaDomainObject DisableField(DisableField command)
{
Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldDisabled { FieldId = fieldId });
RaiseEvent(new FieldDisabled { FieldId = command.FieldId });
return this;
}
public SchemaDomainObject EnableField(long fieldId)
public SchemaDomainObject EnableField(EnableField command)
{
Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldEnabled { FieldId = fieldId });
RaiseEvent(new FieldEnabled { FieldId = command.FieldId });
return this;
}
public SchemaDomainObject DeleteField(long fieldId)
public SchemaDomainObject DeleteField(DeleteField command)
{
Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldDeleted { FieldId = fieldId });
RaiseEvent(new FieldDeleted { FieldId = command.FieldId });
return this;
}
public SchemaDomainObject Delete()
public SchemaDomainObject Delete(DeleteSchema command)
{
VerifyCreatedAndNotDeleted();

40
src/Squidex/Config/Identity/LazyClientStore.cs

@ -38,38 +38,40 @@ namespace Squidex.Config.Identity
{
var client = staticClients.GetOrDefault(clientId);
if (client == null)
if (client != null)
{
return null;
return client;
}
var app = await appProvider.FindAppByNameAsync(clientId);
var token = clientId.Split(':');
if (app != null)
if (token.Length != 2)
{
client = CreateClientFromApp(app);
return null;
}
return client;
}
var app = await appProvider.FindAppByNameAsync(token[0]);
private void CreateStaticClients(IOptions<MyUrlsOptions> urlsOptions)
{
foreach (var client in CreateStaticClients(urlsOptions.Value))
var appClient = app?.Clients.FirstOrDefault(x => x.ClientName == token[1]);
if (appClient == null)
{
staticClients[client.ClientId] = client;
return null;
}
client = CreateClientFromApp(clientId, appClient);
return client;
}
private static Client CreateClientFromApp(IAppEntity app)
private static Client CreateClientFromApp(string id, IAppClientEntity appClient)
{
var id = app.Name;
return new Client
{
ClientId = id,
ClientName = id,
ClientSecrets = app.Clients.Select(x => new Secret(x.ClientName, x.ExpiresUtc)).ToList(),
ClientSecrets = new List<Secret> { new Secret(appClient.ClientSecret.Sha512(), appClient.ExpiresUtc) },
AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds,
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = new List<string>
@ -79,6 +81,14 @@ namespace Squidex.Config.Identity
};
}
private void CreateStaticClients(IOptions<MyUrlsOptions> urlsOptions)
{
foreach (var client in CreateStaticClients(urlsOptions.Value))
{
staticClients[client.ClientId] = client;
}
}
private static IEnumerable<Client> CreateStaticClients(MyUrlsOptions urlsOptions)
{
const string id = Constants.FrontendClient;

27
src/Squidex/Pipeline/RandomErrorAttribute.cs

@ -0,0 +1,27 @@
// ==========================================================================
// RandomErrorFilter.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Squidex.Pipeline
{
public class RandomErrorAttribute : ActionFilterAttribute
{
private static readonly Random random = new Random();
public override void OnActionExecuted(ActionExecutedContext context)
{
if (random.Next(10) < 5)
{
context.Result = new StatusCodeResult(500);
}
}
}
}

2
src/Squidex/Startup.cs

@ -94,7 +94,7 @@ namespace Squidex
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddConsole(LogLevel.Debug);
loggerFactory.AddDebug();
if (!Environment.IsDevelopment())

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

@ -27,6 +27,7 @@ import {
DecimalSeparatorConfig,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
NotificationService,
LanguageService,
LocalStoreService,
SqxFrameworkModule,
@ -88,6 +89,7 @@ export function configCurrency() {
LocalStoreService,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
NotificationService,
TitleService,
UsersProviderService,
UsersService,

2
src/Squidex/app/components/internal/app/settings/clients-page.component.html

@ -51,7 +51,7 @@
</td>
<td>
<button class="btn btn-block btn-danger client-delete" (click)="revokeClient(client)">Revoke</button>
<button class="btn btn-block btn-default">Create Token</button>
<button class="btn btn-block btn-default" (click)="createToken(client)">Create Token</button>
</td>
</tr>
</table>

26
src/Squidex/app/components/internal/app/settings/clients-page.component.ts

@ -14,6 +14,8 @@ import {
AppClientCreateDto,
AppClientsService,
fadeAnimation,
Notification,
NotificationService,
TitleService
} from 'shared';
@ -46,7 +48,8 @@ export class ClientsPageComponent implements Ng2.OnInit {
private readonly titles: TitleService,
private readonly appsStore: AppsStoreService,
private readonly appClientsService: AppClientsService,
private readonly formBuilder: Ng2Forms.FormBuilder
private readonly formBuilder: Ng2Forms.FormBuilder,
private readonly notifications: NotificationService
) {
}
@ -58,9 +61,13 @@ export class ClientsPageComponent implements Ng2.OnInit {
this.titles.setTitle('{appName} | Settings | Clients', { appName: app.name });
this.appClientsService.getClients(app.name).subscribe(clients => {
this.appClients = clients;
});
this.appClientsService.getClients(app.name)
.subscribe(clients => {
this.appClients = clients;
}, () => {
this.notifications.notify(Notification.error('Failed to load clients. Please reload squidex portal.'));
this.appClients = [];
});
}
});
}
@ -74,9 +81,16 @@ export class ClientsPageComponent implements Ng2.OnInit {
}
public revokeClient(client: AppClientDto) {
this.appClientsService.deleteClient(this.appName, client.clientName).subscribe();
this.appClientsService.deleteClient(this.appName, client.clientName)
.subscribe(() => {
this.appClients.splice(this.appClients.indexOf(client), 1);
}, error => {
this.notifications.notify(Notification.error('Failed to revoke client. Please retry.'));
});
}
this.appClients.splice(this.appClients.indexOf(client), 1);
public createToken(client: AppClientDto) {
this.appClientsService.createToken(this.appName, client).subscribe();
}
public attachClient() {

38
src/Squidex/app/components/internal/app/settings/contributors-page.component.ts

@ -16,6 +16,8 @@ import {
AppContributorsService,
AppsStoreService,
AuthService,
Notification,
NotificationService,
TitleService,
UserDto,
UsersService,
@ -27,7 +29,7 @@ class UsersDataSource extends CompleterBaseData {
constructor(
private readonly usersService: UsersService,
private readonly component: ContributorsPageComponent,
private readonly component: ContributorsPageComponent
) {
super();
}
@ -93,7 +95,8 @@ export class ContributorsPageComponent implements Ng2.OnInit {
private readonly appsStore: AppsStoreService,
private readonly appContributorsService: AppContributorsService,
private readonly usersProvider: UsersProviderService,
private readonly usersService: UsersService
private readonly usersService: UsersService,
private readonly notifications: NotificationService
) {
this.usersDataSource = new UsersDataSource(usersService, this);
}
@ -108,9 +111,12 @@ export class ContributorsPageComponent implements Ng2.OnInit {
this.titles.setTitle('{appName} | Settings | Contributors', { appName: app.name });
this.appContributorsService.getContributors(app.name).subscribe(contributors => {
this.appContributors = contributors;
});
this.appContributorsService.getContributors(app.name).retry(2)
.subscribe(contributors => {
this.appContributors = contributors;
}, error => {
this.notifications.notify(Notification.error('Failed to load app contributors. Please reload squidex portal.'));
});
}
});
}
@ -126,7 +132,13 @@ export class ContributorsPageComponent implements Ng2.OnInit {
const contributor = new AppContributorDto(this.selectedUser.id, 'Editor');
this.appContributorsService.postContributor(this.appName, contributor).subscribe();
this.appContributorsService.postContributor(this.appName, contributor)
.catch(error => {
this.notifications.notify(Notification.error('Failed to assign contributors. Please retry.'));
return Observable.of(true);
}).subscribe();
this.appContributors.push(contributor);
this.selectedUser = null;
@ -134,13 +146,23 @@ export class ContributorsPageComponent implements Ng2.OnInit {
}
public removeContributor(contributor: AppContributorDto) {
this.appContributorsService.deleteContributor(this.appName, contributor.contributorId).subscribe();
this.appContributorsService.deleteContributor(this.appName, contributor.contributorId)
.catch(error => {
this.notifications.notify(Notification.error('Failed to remove contributors. Please retry.'));
return Observable.of(true);
}).subscribe();
this.appContributors.splice(this.appContributors.indexOf(contributor), 1);
}
public saveContributor(contributor: AppContributorDto) {
this.appContributorsService.postContributor(this.appName, contributor).subscribe();
this.appContributorsService.postContributor(this.appName, contributor)
.catch(error => {
this.notifications.notify(Notification.error('Failed to update contributors. Please retry.'));
return Observable.of(true);
}).subscribe();
}
public selectUser(selection: CompleterItem | null) {

2
src/Squidex/app/components/internal/app/settings/languages-page.component.html

@ -4,7 +4,7 @@
</div>
<div class="layout-middle">
<div class="layout-middle-header">
<div class="pull-right">
<div class="float-xs-right">
<button class="btn btn-success" (click)="saveLanguages()" [disabled]="isSaving">{{isSaving ? 'Saving...' : 'Save Changes'}}</button>
</div>

34
src/Squidex/app/components/internal/app/settings/languages-page.component.ts

@ -12,6 +12,8 @@ import {
AppsStoreService,
LanguageDto,
LanguageService,
Notification,
NotificationService,
TitleService
} from 'shared';
@ -38,14 +40,18 @@ export class LanguagesPageComponent implements Ng2.OnInit {
private readonly titles: TitleService,
private readonly appsStore: AppsStoreService,
private readonly appLanguagesService: AppLanguagesService,
private readonly languagesService: LanguageService
private readonly languagesService: LanguageService,
private readonly notifications: NotificationService
) {
}
public ngOnInit() {
this.languagesService.getLanguages().subscribe(languages => {
this.allLanguages = languages;
});
this.languagesService.getLanguages().retry(2)
.subscribe(languages => {
this.allLanguages = languages;
}, error => {
this.notifications.notify(Notification.error('Failed to load languages. Please reload squidex portal.'));
});
this.appSubscription =
this.appsStore.selectedApp.subscribe(app => {
@ -54,9 +60,12 @@ export class LanguagesPageComponent implements Ng2.OnInit {
this.titles.setTitle('{appName} | Settings | Languages', { appName: app.name });
this.appLanguagesService.getLanguages(app.name).subscribe(appLanguages => {
this.appLanguages = appLanguages;
});
this.appLanguagesService.getLanguages(app.name).retry(2)
.subscribe(appLanguages => {
this.appLanguages = appLanguages;
}, error => {
this.notifications.notify(Notification.error('Failed to load app languages. Please reload squidex portal.'));
});
}
});
}
@ -80,10 +89,13 @@ export class LanguagesPageComponent implements Ng2.OnInit {
this.appLanguagesService.postLanguages(this.appName, this.appLanguages.map(l => l.iso2Code))
.delay(500)
.finally(() => {
this.isSaving = false;
})
.subscribe();
.subscribe(() => {
this.isSaving = false;
}, error => {
this.isSaving = false;
this.notifications.notify(Notification.error('Failed to save app languages. Please retry.'));
});
}
}

6
src/Squidex/app/components/internal/internal-area.component.html

@ -17,3 +17,9 @@
</nav>
<router-outlet></router-outlet>
<div class="notification-container">
<div class="notification-item notification-{{notification.messageType}}" *ngFor="let notification of notifications" (click)="close(notification)" [@fade]>
{{notification.message}}
</div>
</div>

22
src/Squidex/app/components/internal/internal-area.component.scss

@ -15,4 +15,26 @@
.search-form {
margin-left: 15px;
}
.notification {
&-container {
@include fixed(auto, 10px, 10px, auto);
width: 260px;
}
&-item {
@include border-radius;
@include box-shadow;
padding: 10px;
margin-top: 10px;
font-size: .8rem;
font-weight: normal;
color: $color-accent-dark;
cursor: pointer;
}
&-error {
background: $color-theme-error;
}
}

43
src/Squidex/app/components/internal/internal-area.component.ts

@ -7,9 +7,48 @@
import * as Ng2 from '@angular/core';
import {
fadeAnimation,
Notification,
NotificationService
} from 'shared';
@Ng2.Component({
selector: 'sqx-internal-area',
styles,
template
template,
animations: [
fadeAnimation
]
})
export class InternalAreaComponent { }
export class InternalAreaComponent implements Ng2.OnInit, Ng2.OnDestroy {
private notificationsSubscription: any;
public notifications: Notification[] = [];
constructor(
private readonly notificationService: NotificationService
) {
}
public ngOnInit() {
this.notificationsSubscription =
this.notificationService.notifications.subscribe(notification => {
this.notifications.push(notification);
if (notification.displayTime > 0) {
setTimeout(() => {
this.close(notification);
}, notification.displayTime);
}
});
}
public ngOnDestroy() {
this.notificationsSubscription.unsubscribe();
}
public close(notification: Notification) {
this.notifications.splice(this.notifications.indexOf(notification), 1);
}
}

4
src/Squidex/app/components/layout/app-form.component.html

@ -32,7 +32,7 @@
</div>
<div class="form-group clearfix">
<button type="reset" class="pull-left btn btn-secondary" (click)="cancel()">Cancel</button>
<button type="submit" class="pull-right btn btn-primary" [disabled]="createForm.invalid">Create</button>
<button type="reset" class="float-xs-left btn btn-secondary" (click)="cancel()">Cancel</button>
<button type="submit" class="float-xs-right btn btn-primary" [disabled]="createForm.invalid">Create</button>
</div>
</form>

1
src/Squidex/app/framework/declarations.ts

@ -25,6 +25,7 @@ export * from './configurations';
export * from './services/clipboard.service';
export * from './services/drag.service';
export * from './services/local-store.service';
export * from './services/notification.service';
export * from './services/shortcut.service';
export * from './services/title.service';

1
src/Squidex/app/framework/services/clipboard.service.spec.ts

@ -8,7 +8,6 @@
import { ClipboardService, ClipboardServiceFactory } from './../';
describe('ShortcutService', () => {
it('should instantiate from factory', () => {
const clipboardService = ClipboardServiceFactory();

41
src/Squidex/app/framework/services/notification.service.spec.ts

@ -0,0 +1,41 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import {
Notification,
NotificationService,
NotificationServiceFactory
} from './../';
describe('NotificationService', () => {
it('should instantiate from factory', () => {
const notificationService = NotificationServiceFactory();
expect(notificationService).toBeDefined();
});
it('should instantiate', () => {
const notificationService = new NotificationService();
expect(notificationService).toBeDefined();
});
it('should publish notification', () => {
const notificationService = new NotificationService();
const notification = Notification.error('Message');
let publishedNotification: Notification;
notificationService.notifications.subscribe(result => {
publishedNotification = result;
});
notificationService.notify(notification);
expect(publishedNotification).toBe(notification);
});
});

44
src/Squidex/app/framework/services/notification.service.ts

@ -0,0 +1,44 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { Observable, Subject } from 'rxjs';
export const NotificationServiceFactory = () => {
return new NotificationService();
};
export class Notification {
constructor(
public readonly message: string,
public readonly messageType: string,
public readonly displayTime: number = 10000
) {
}
public static error(message: string) {
return new Notification(message, 'error');
}
public static info(message: string) {
return new Notification(message, 'info');
}
}
@Ng2.Injectable()
export class NotificationService {
private readonly notificationsStream$ = new Subject<Notification>();
public get notifications(): Observable<Notification> {
return this.notificationsStream$;
}
public notify(notification: Notification) {
this.notificationsStream$.next(notification);
}
}

2
src/Squidex/app/shared/services/app-clients.service.spec.ts

@ -25,7 +25,7 @@ describe('AppClientsService', () => {
beforeEach(() => {
authService = TypeMoq.Mock.ofType(AuthService);
appClientsService = new AppClientsService(authService.object, new ApiUrlConfig('http://service/p/'));
appClientsService = new AppClientsService(authService.object, new ApiUrlConfig('http://service/p/'), null);
});
it('should make get request with auth service to get app clients', () => {

16
src/Squidex/app/shared/services/app-clients.service.ts

@ -6,6 +6,7 @@
*/
import * as Ng2 from '@angular/core';
import * as Ng2Http from '@angular/http';
import { Observable } from 'rxjs';
@ -32,7 +33,8 @@ export class AppClientCreateDto {
export class AppClientsService {
constructor(
private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig
private readonly apiUrl: ApiUrlConfig,
private readonly http: Ng2Http.Http
) {
}
@ -64,4 +66,16 @@ export class AppClientsService {
public deleteClient(appName: string, name: string): Observable<any> {
return this.authService.authDelete(this.apiUrl.buildUrl(`api/apps/${appName}/clients/${name}`));
}
public createToken(appName: string, client: AppClientDto): Observable<any> {
const options = new Ng2Http.RequestOptions({
headers: new Ng2Http.Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
});
const body = `grant_type=client_credentials&scope=squidex-api&client_id=${appName}:${client.clientName}&client_secret=${client.clientSecret}`;
return this.http.post(this.apiUrl.buildUrl('identity-server/connect/token'), body, options);
}
}

2
src/Squidex/app/shared/services/users-provider.service.ts

@ -28,7 +28,7 @@ export class UsersProviderService {
if (!result) {
const request =
this.usersService.getUser(id)
this.usersService.getUser(id).retry(2)
.map(u => {
if (this.authService.user && u.id === this.authService.user.id) {
return new UserDto(u.id, u.email, 'Me', u.pictureUrl);

16
tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs

@ -46,7 +46,7 @@ namespace Squidex.Write.Tests.Apps
}
[Fact]
public void Create_should_throw_if_command_is_invalid()
public void Create_should_throw_if_command_is_not_valid()
{
Assert.Throws<ValidationException>(() => sut.Create(new CreateApp()));
}
@ -76,9 +76,9 @@ namespace Squidex.Write.Tests.Apps
}
[Fact]
public void AssignContributor_should_throw_if_contributor_id_not_valid()
public void AssignContributor_should_throw_if_command_is_not_valid()
{
Assert.Throws<DomainException>(() => sut.AssignContributor(new AssignContributor()));
Assert.Throws<ValidationException>(() => sut.AssignContributor(new AssignContributor()));
}
[Fact]
@ -113,9 +113,9 @@ namespace Squidex.Write.Tests.Apps
}
[Fact]
public void RemoveContributor_should_throw_if_contributor_id_not_valid()
public void RemoveContributor_should_throw_if_command_is_not_valid()
{
Assert.Throws<DomainException>(() => sut.RemoveContributor(new RemoveContributor()));
Assert.Throws<ValidationException>(() => sut.RemoveContributor(new RemoveContributor()));
}
[Fact]
@ -161,7 +161,7 @@ namespace Squidex.Write.Tests.Apps
}
[Fact]
public void ConfigureLanguages_should_throw_if_languages_are_null_or_empty()
public void ConfigureLanguages_should_throw_if_command_is_not_valid()
{
CreateApp();
@ -193,7 +193,7 @@ namespace Squidex.Write.Tests.Apps
}
[Fact]
public void AttachClient_should_throw_if_name_not_valid()
public void AttachClient_should_throw_if_command_is_not_valid()
{
CreateApp();
@ -237,7 +237,7 @@ namespace Squidex.Write.Tests.Apps
}
[Fact]
public void RevokeClient_should_throw_if_client_key_is_null_or_empty()
public void RevokeClient_should_throw_if_command_is_not_valid()
{
CreateApp();

358
tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs

@ -12,18 +12,21 @@ using FluentAssertions;
using Squidex.Core.Schemas;
using Squidex.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Write.Schemas;
using Squidex.Write.Schemas.Commands;
using Xunit;
// ReSharper disable ConvertToConstant.Local
namespace Squidex.Write.Tests.Schemas
{
[Collection("Schema")]
public class SchemaDomainObjectTests
{
private const string TestName = "schema";
private readonly Guid appId = Guid.NewGuid();
private readonly string fieldName = "age";
private readonly string appName = "schema";
private readonly FieldRegistry registry = new FieldRegistry();
private readonly SchemaDomainObject sut;
@ -35,23 +38,23 @@ namespace Squidex.Write.Tests.Schemas
[Fact]
public void Create_should_throw_if_created()
{
sut.Create(new CreateSchema { Name = TestName });
sut.Create(new CreateSchema { Name = appName });
Assert.Throws<DomainException>(() => sut.Create(new CreateSchema { Name = TestName }));
Assert.Throws<DomainException>(() => sut.Create(new CreateSchema { Name = appName }));
}
[Fact]
public void Create_should_throw_if_command_is_invalid()
public void Create_should_throw_if_command_is_not_valid()
{
Assert.Throws<ValidationException>(() => sut.Create(new CreateSchema()));
}
[Fact]
public void Create_should_create_schema()
public void Create_should_create_schema_and_create_events()
{
var properties = new SchemaProperties();
sut.Create(new CreateSchema { Name = TestName, AppId = appId, Properties = properties });
sut.Create(new CreateSchema { Name = appName, AppId = appId, Properties = properties });
Assert.Equal("schema", sut.Schema.Name);
@ -59,7 +62,7 @@ namespace Squidex.Write.Tests.Schemas
.ShouldBeEquivalentTo(
new IEvent[]
{
new SchemaCreated { Name = TestName, AppId = appId, Properties = properties }
new SchemaCreated { Name = appName, AppId = appId, Properties = properties }
});
}
@ -72,31 +75,32 @@ namespace Squidex.Write.Tests.Schemas
[Fact]
public void Update_should_throw_if_schema_is_deleted()
{
sut.Create(new CreateSchema { Name = TestName });
sut.Delete();
CreateSchema();
DeleteSchema();
Assert.Throws<ValidationException>(() => sut.Update(new UpdateSchema()));
}
[Fact]
public void Update_should_throw_if_command_is_invalid()
public void Update_should_throw_if_command_is_not_valid()
{
sut.Create(new CreateSchema { Name = TestName });
CreateSchema();
Assert.Throws<ValidationException>(() => sut.Update(new UpdateSchema()));
}
[Fact]
public void Update_should_refresh_properties()
public void Update_should_refresh_properties_and_create_events()
{
var properties = new SchemaProperties();
sut.Create(new CreateSchema { Name = TestName, AppId = appId });
CreateSchema();
sut.Update(new UpdateSchema { Properties = properties });
Assert.Equal(properties, sut.Schema.Properties);
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
@ -107,32 +111,342 @@ namespace Squidex.Write.Tests.Schemas
[Fact]
public void Delete_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.Delete());
Assert.Throws<DomainException>(() => sut.Delete(new DeleteSchema()));
}
[Fact]
public void Delete_should_throw_if_already_deleted()
{
sut.Create(new CreateSchema { Name = TestName });
sut.Delete();
CreateSchema();
DeleteSchema();
Assert.Throws<DomainException>(() => sut.Delete());
Assert.Throws<DomainException>(() => sut.Delete(new DeleteSchema()));
}
[Fact]
public void Delete_should_refresh_properties()
public void Delete_should_refresh_properties_and_create_events()
{
sut.Create(new CreateSchema { Name = TestName, AppId = appId });
sut.Delete();
CreateSchema();
sut.Delete(new DeleteSchema());
Assert.True(sut.IsDeleted);
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new SchemaDeleted()
});
}
[Fact]
public void AddField_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.AddField(new AddField { Name = fieldName, Properties = new NumberFieldProperties() }));
}
[Fact]
public void AddField_should_throw_if_command_is_not_valid()
{
Assert.Throws<ValidationException>(() => sut.AddField(new AddField()));
}
[Fact]
public void AddField_should_throw_if_schema_is_deleted()
{
CreateSchema();
DeleteSchema();
Assert.Throws<DomainException>(() => sut.AddField(new AddField { Name = fieldName, Properties = new NumberFieldProperties() }));
}
[Fact]
public void AddField_should_update_schema_and_create_events()
{
var properties = new NumberFieldProperties();
CreateSchema();
sut.AddField(new AddField { Name = fieldName, Properties = properties });
Assert.Equal(properties, sut.Schema.Fields[1].RawProperties);
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new FieldAdded { Name = fieldName, FieldId = 1, Properties = properties }
});
}
[Fact]
public void UpdateField_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.UpdateField(new UpdateField { FieldId = 1, Properties = new NumberFieldProperties() }));
}
[Fact]
public void UpdateField_should_throw_if_command_is_not_valid()
{
Assert.Throws<ValidationException>(() => sut.UpdateField(new UpdateField()));
}
[Fact]
public void UpdateField_should_throw_if_field_is_not_found()
{
CreateSchema();
Assert.Throws<DomainObjectNotFoundException>(() => sut.UpdateField(new UpdateField { FieldId = 1, Properties = new NumberFieldProperties() }));
}
[Fact]
public void UpdateField_should_throw_if_schema_is_deleted()
{
CreateSchema();
DeleteSchema();
Assert.Throws<DomainException>(() => sut.UpdateField(new UpdateField { FieldId = 1, Properties = new NumberFieldProperties() }));
}
[Fact]
public void UpdateField_should_update_schema_and_create_events()
{
var properties = new NumberFieldProperties();
CreateSchema();
CreateField();
sut.UpdateField(new UpdateField { FieldId = 1, Properties = properties });
Assert.Equal(properties, sut.Schema.Fields[1].RawProperties);
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new FieldUpdated { FieldId = 1, Properties = properties }
});
}
[Fact]
public void HideField_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.HideField(new HideField { FieldId = 1 }));
}
[Fact]
public void HideField_should_throw_if_field_is_not_found()
{
CreateSchema();
Assert.Throws<DomainObjectNotFoundException>(() => sut.HideField(new HideField { FieldId = 2 }));
}
[Fact]
public void HideField_should_throw_if_schema_is_deleted()
{
CreateSchema();
DeleteSchema();
Assert.Throws<DomainException>(() => sut.HideField(new HideField { FieldId = 1 }));
}
[Fact]
public void HideField_should_update_schema_and_create_events()
{
CreateSchema();
CreateField();
sut.HideField(new HideField { FieldId = 1 });
Assert.True(sut.Schema.Fields[1].IsHidden);
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new FieldHidden { FieldId = 1 }
});
}
[Fact]
public void ShowField_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.ShowField(new ShowField { FieldId = 1 }));
}
[Fact]
public void ShowField_should_throw_if_field_is_not_found()
{
CreateSchema();
Assert.Throws<DomainObjectNotFoundException>(() => sut.ShowField(new ShowField { FieldId = 2 }));
}
[Fact]
public void ShowField_should_throw_if_schema_is_deleted()
{
CreateSchema();
DeleteSchema();
Assert.Throws<DomainException>(() => sut.ShowField(new ShowField { FieldId = 1 }));
}
[Fact]
public void ShowField_should_update_schema_and_create_events()
{
CreateSchema();
CreateField();
sut.HideField(new HideField { FieldId = 1 });
sut.ShowField(new ShowField { FieldId = 1 });
Assert.False(sut.Schema.Fields[1].IsHidden);
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new FieldShown { FieldId = 1 }
});
}
[Fact]
public void DisableField_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.DisableField(new DisableField { FieldId = 1 }));
}
[Fact]
public void DisableField_should_throw_if_field_is_not_found()
{
CreateSchema();
Assert.Throws<DomainObjectNotFoundException>(() => sut.DisableField(new DisableField { FieldId = 2 }));
}
[Fact]
public void DisableField_should_throw_if_schema_is_deleted()
{
CreateSchema();
DeleteSchema();
Assert.Throws<DomainException>(() => sut.DisableField(new DisableField { FieldId = 1 }));
}
[Fact]
public void DisableField_should_update_schema_and_create_events()
{
CreateSchema();
CreateField();
sut.DisableField(new DisableField { FieldId = 1 });
Assert.True(sut.Schema.Fields[1].IsDisabled);
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new FieldDisabled { FieldId = 1 }
});
}
[Fact]
public void EnableField_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.EnableField(new EnableField { FieldId = 1 }));
}
[Fact]
public void EnableField_should_throw_if_field_is_not_found()
{
CreateSchema();
Assert.Throws<DomainObjectNotFoundException>(() => sut.EnableField(new EnableField { FieldId = 2 }));
}
[Fact]
public void EnableField_should_throw_if_schema_is_deleted()
{
CreateSchema();
DeleteSchema();
Assert.Throws<DomainException>(() => sut.EnableField(new EnableField { FieldId = 1 }));
}
[Fact]
public void EnableField_should_update_schema_and_create_events()
{
CreateSchema();
CreateField();
sut.DisableField(new DisableField { FieldId = 1 });
sut.EnableField(new EnableField { FieldId = 1 });
Assert.False(sut.Schema.Fields[1].IsDisabled);
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new FieldEnabled { FieldId = 1 }
});
}
[Fact]
public void DeleteField_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.DeleteField(new DeleteField { FieldId = 1 }));
}
[Fact]
public void DeleteField_should_throw_if_schema_is_deleted()
{
CreateSchema();
DeleteSchema();
Assert.Throws<DomainException>(() => sut.DeleteField(new DeleteField { FieldId = 1 }));
}
[Fact]
public void DeleteField_should_update_schema_and_create_events()
{
CreateSchema();
CreateField();
sut.DeleteField(new DeleteField { FieldId = 1 });
Assert.False(sut.Schema.Fields.ContainsKey(1));
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new FieldDeleted { FieldId = 1 }
});
}
private void CreateField()
{
sut.AddField(new AddField { Name = fieldName, Properties = new NumberFieldProperties() });
((IAggregate)sut).ClearUncommittedEvents();
}
private void CreateSchema()
{
sut.Create(new CreateSchema { Name = appName, AppId = appId });
((IAggregate)sut).ClearUncommittedEvents();
}
private void DeleteSchema()
{
sut.Delete(new DeleteSchema());
((IAggregate)sut).ClearUncommittedEvents();
}
}
}

Loading…
Cancel
Save