diff --git a/src/Squidex/app/shared/services/schemas.fields.spec.ts b/src/Squidex/app/shared/services/schemas.fields.spec.ts
new file mode 100644
index 000000000..92bf4a9d4
--- /dev/null
+++ b/src/Squidex/app/shared/services/schemas.fields.spec.ts
@@ -0,0 +1,175 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Sebastian Stehle. All rights reserved
+ */
+
+import {
+ AssetsFieldPropertiesDto,
+ BooleanFieldPropertiesDto,
+ DateTimeFieldPropertiesDto,
+ FieldDto,
+ FieldPropertiesDto,
+ GeolocationFieldPropertiesDto,
+ JsonFieldPropertiesDto,
+ NumberFieldPropertiesDto,
+ ReferencesFieldPropertiesDto,
+ StringFieldPropertiesDto
+} from './../';
+
+describe('AssetsField', () => {
+ const field = createField(new AssetsFieldPropertiesDto(null, null, null, true, false, 1, 1));
+
+ it('should create validators', () => {
+ expect(field.createValidators().length).toBe(3);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(field.formatValue(null)).toBe('');
+ });
+
+ it('should format to asset count', () => {
+ expect(field.formatValue([1, 2, 3])).toBe('3 Asset(s)');
+ });
+
+ it('should return zero formatting if other type', () => {
+ expect(field.formatValue(1)).toBe('0 Assets');
+ });
+});
+
+describe('BooleanField', () => {
+ const field = createField(new BooleanFieldPropertiesDto(null, null, null, true, false, 'Checkbox'));
+
+ it('should create validators', () => {
+ expect(field.createValidators().length).toBe(1);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(field.formatValue(null)).toBe('');
+ });
+
+ it('should format to checkmark if true', () => {
+ expect(field.formatValue(true)).toBe('✔');
+ });
+
+ it('should format to minus if false', () => {
+ expect(field.formatValue(false)).toBe('-');
+ });
+});
+
+describe('DateTimeField', () => {
+ const field = createField(new DateTimeFieldPropertiesDto(null, null, null, true, false, 'Date'));
+
+ it('should create validators', () => {
+ expect(field.createValidators().length).toBe(1);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(field.formatValue(null)).toBe('');
+ });
+
+ it('should format to input if parsing failed', () => {
+ expect(field.formatValue(true)).toBe(true);
+ });
+
+ it('should format to date', () => {
+ const dateField = createField(new DateTimeFieldPropertiesDto(null, null, null, true, false, 'Date'));
+
+ expect(dateField.formatValue('2017-12-12T16:00:00Z')).toBe('2017-12-12');
+ });
+
+ it('should format to date', () => {
+ const dateTimeField = createField(new DateTimeFieldPropertiesDto(null, null, null, true, false, 'DateTime'));
+
+ expect(dateTimeField.formatValue('2017-12-12T16:00:00Z').substr(0, 10)).toBe('2017-12-12');
+ });
+});
+
+describe('GeolocationField', () => {
+ const field = createField(new GeolocationFieldPropertiesDto(null, null, null, true, false, 'Default'));
+
+ it('should create validators', () => {
+ expect(field.createValidators().length).toBe(1);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(field.formatValue(null)).toBe('');
+ });
+
+ it('should format to latitude and longitude', () => {
+ expect(field.formatValue({ latitude: 42, longitude: 3.14 })).toBe('3.14, 42');
+ });
+});
+
+describe('JsonField', () => {
+ const field = createField(new JsonFieldPropertiesDto(null, null, null, true, false));
+
+ it('should create validators', () => {
+ expect(field.createValidators().length).toBe(1);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(field.formatValue(null)).toBe('');
+ });
+
+ it('should format to constant', () => {
+ expect(field.formatValue({})).toBe('');
+ });
+});
+
+describe('NumberField', () => {
+ const field = createField(new NumberFieldPropertiesDto(null, null, null, true, false, 'Input', undefined, 3, 1, [1, 2, 3]));
+
+ it('should create validators', () => {
+ expect(field.createValidators().length).toBe(4);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(field.formatValue(null)).toBe('');
+ });
+
+ it('should format to number', () => {
+ expect(field.formatValue(42)).toBe(42);
+ });
+});
+
+describe('ReferencesField', () => {
+ const field = createField(new ReferencesFieldPropertiesDto(null, null, null, true, false, 1, 1));
+
+ it('should create validators', () => {
+ expect(field.createValidators().length).toBe(3);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(field.formatValue(null)).toBe('');
+ });
+
+ it('should format to asset count', () => {
+ expect(field.formatValue([1, 2, 3])).toBe('3 Reference(s)');
+ });
+
+ it('should return zero formatting if other type', () => {
+ expect(field.formatValue(1)).toBe('0 References');
+ });
+});
+
+describe('NumberField', () => {
+ const field = createField(new StringFieldPropertiesDto(null, null, null, true, false, 'Input', undefined, 'pattern', undefined, 3, 1, ['1', '2']));
+
+ it('should create validators', () => {
+ expect(field.createValidators().length).toBe(5);
+ });
+
+ it('should format to empty string if null', () => {
+ expect(field.formatValue(null)).toBe('');
+ });
+
+ it('should format to string', () => {
+ expect(field.formatValue('hello')).toBe('hello');
+ });
+});
+
+function createField(properties: FieldPropertiesDto) {
+ return new FieldDto(1, 'field1', false, false, 'languages', properties);
+}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/services/usages.service.spec.ts b/src/Squidex/app/shared/services/usages.service.spec.ts
new file mode 100644
index 000000000..6aea7e98d
--- /dev/null
+++ b/src/Squidex/app/shared/services/usages.service.spec.ts
@@ -0,0 +1,143 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Sebastian Stehle. All rights reserved
+ */
+
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { inject, TestBed } from '@angular/core/testing';
+
+import {
+ ApiUrlConfig,
+ CallsUsageDto,
+ CurrentCallsDto,
+ CurrentStorageDto,
+ DateTime,
+ StorageUsageDto,
+ UsagesService
+} from './../';
+
+describe('UsagesService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ HttpClientTestingModule
+ ],
+ providers: [
+ UsagesService,
+ { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
+ ]
+ });
+ });
+
+ afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
+ httpMock.verify();
+ }));
+
+ it('should make get request to get calls usages',
+ inject([UsagesService, HttpTestingController], (usagesService: UsagesService, httpMock: HttpTestingController) => {
+
+ let usages: CallsUsageDto[] | null = null;
+
+ usagesService.getCallsUsages('my-app', DateTime.parseISO_UTC('2017-10-12'), DateTime.parseISO_UTC('2017-10-13')).subscribe(result => {
+ usages = result;
+ });
+
+ const req = httpMock.expectOne('http://service/p/api/apps/my-app/usages/calls/2017-10-12/2017-10-13');
+
+ expect(req.request.method).toEqual('GET');
+ expect(req.request.headers.get('If-Match')).toBeNull();
+
+ req.flush([
+ {
+ date: '2017-10-12',
+ count: 10,
+ averageMs: 130
+ },
+ {
+ date: '2017-10-13',
+ count: 13,
+ averageMs: 170
+ }
+ ]);
+
+ expect(usages).toEqual(
+ [
+ new CallsUsageDto(DateTime.parseISO_UTC('2017-10-12'), 10, 130),
+ new CallsUsageDto(DateTime.parseISO_UTC('2017-10-13'), 13, 170)
+ ]);
+ }));
+
+ it('should make get request to get month calls',
+ inject([UsagesService, HttpTestingController], (usagesService: UsagesService, httpMock: HttpTestingController) => {
+
+ let usages: CurrentCallsDto | null = null;
+
+ usagesService.getMonthCalls('my-app').subscribe(result => {
+ usages = result;
+ });
+
+ const req = httpMock.expectOne('http://service/p/api/apps/my-app/usages/calls/month');
+
+ expect(req.request.method).toEqual('GET');
+ expect(req.request.headers.get('If-Match')).toBeNull();
+
+ req.flush({ count: 130, maxAllowed: 150 });
+
+ expect(usages).toEqual(new CurrentCallsDto(130, 150));
+ }));
+
+ it('should make get request to get storage usages',
+ inject([UsagesService, HttpTestingController], (usagesService: UsagesService, httpMock: HttpTestingController) => {
+
+ let usages: StorageUsageDto[] | null = null;
+
+ usagesService.getStorageUsages('my-app', DateTime.parseISO_UTC('2017-10-12'), DateTime.parseISO_UTC('2017-10-13')).subscribe(result => {
+ usages = result;
+ });
+
+ const req = httpMock.expectOne('http://service/p/api/apps/my-app/usages/storage/2017-10-12/2017-10-13');
+
+ expect(req.request.method).toEqual('GET');
+ expect(req.request.headers.get('If-Match')).toBeNull();
+
+ req.flush([
+ {
+ date: '2017-10-12',
+ count: 10,
+ size: 130
+ },
+ {
+ date: '2017-10-13',
+ count: 13,
+ size: 170
+ }
+ ]);
+
+ expect(usages).toEqual(
+ [
+ new StorageUsageDto(DateTime.parseISO_UTC('2017-10-12'), 10, 130),
+ new StorageUsageDto(DateTime.parseISO_UTC('2017-10-13'), 13, 170)
+ ]);
+ }));
+
+ it('should make get request to get today storage',
+ inject([UsagesService, HttpTestingController], (usagesService: UsagesService, httpMock: HttpTestingController) => {
+
+ let usages: CurrentStorageDto | null = null;
+
+ usagesService.getTodayStorage('my-app').subscribe(result => {
+ usages = result;
+ });
+
+ const req = httpMock.expectOne('http://service/p/api/apps/my-app/usages/storage/today');
+
+ expect(req.request.method).toEqual('GET');
+ expect(req.request.headers.get('If-Match')).toBeNull();
+
+ req.flush({ size: 130, maxAllowed: 150 });
+
+ expect(usages).toEqual(new CurrentStorageDto(130, 150));
+ }));
+});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/services/users-provider.service.spec.ts b/src/Squidex/app/shared/services/users-provider.service.spec.ts
new file mode 100644
index 000000000..ba1c62647
--- /dev/null
+++ b/src/Squidex/app/shared/services/users-provider.service.spec.ts
@@ -0,0 +1,103 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Sebastian Stehle. All rights reserved
+ */
+
+import { Observable } from 'rxjs';
+import { IMock, Mock, Times } from 'typemoq';
+
+import {
+ AuthService,
+ Profile,
+ UserDto,
+ UsersProviderService,
+ UsersService
+} from './../';
+
+describe('UsersProviderService', () => {
+ let authService: IMock;
+ let usersService: IMock;
+ let usersProviderService: UsersProviderService;
+
+ beforeEach(() => {
+ authService = Mock.ofType(AuthService);
+ usersService = Mock.ofType(UsersService);
+ usersProviderService = new UsersProviderService(usersService.object, authService.object);
+ });
+
+ it('Should return users service when user not cached', () => {
+ const user = new UserDto('123', 'mail@domain.com', 'User1', 'path/to/image', true);
+
+ usersService.setup(x => x.getUser('123'))
+ .returns(() => Observable.of(user)).verifiable(Times.once());
+
+ let resultingUser: UserDto | null = null;
+
+ usersProviderService.getUser('123').subscribe(result => {
+ resultingUser = result;
+ }).unsubscribe();
+
+ expect(resultingUser).toBe(user);
+
+ usersService.verifyAll();
+ });
+
+ it('Should return provide user from cache', () => {
+ const user = new UserDto('123', 'mail@domain.com', 'User1', 'path/to/image', true);
+
+ usersService.setup(x => x.getUser('123'))
+ .returns(() => Observable.of(user)).verifiable(Times.once());
+
+ usersProviderService.getUser('123');
+
+ let resultingUser: UserDto | null = null;
+
+ usersProviderService.getUser('123').subscribe(result => {
+ resultingUser = result;
+ }).unsubscribe();
+
+ expect(resultingUser).toBe(user);
+
+ usersService.verifyAll();
+ });
+
+ it('Should return me when user is current user', () => {
+ const user = new UserDto('123', 'mail@domain.com', 'User1', 'path/to/image', true);
+
+ authService.setup(x => x.user)
+ .returns(() => new Profile({ profile: { sub: '123'}}));
+
+ usersService.setup(x => x.getUser('123'))
+ .returns(() => Observable.of(user)).verifiable(Times.once());
+
+ let resultingUser: UserDto | null = null;
+
+ usersProviderService.getUser('123').subscribe(result => {
+ resultingUser = result;
+ }).unsubscribe();
+
+ expect(resultingUser).toEqual(new UserDto('123', 'mail@domain.com', 'Me', 'path/to/image', true));
+
+ usersService.verifyAll();
+ });
+
+ it('Should return invalid user when not found', () => {
+ authService.setup(x => x.user)
+ .returns(() => new Profile({ profile: { sub: '123'}}));
+
+ usersService.setup(x => x.getUser('123'))
+ .returns(() => Observable.throw('NOT FOUND')).verifiable(Times.once());
+
+ let resultingUser: UserDto | null = null;
+
+ usersProviderService.getUser('123').subscribe(result => {
+ resultingUser = result;
+ }).unsubscribe();
+
+ expect(resultingUser).toEqual(new UserDto('NOT FOUND', 'NOT FOUND', 'NOT FOUND', null, false));
+
+ usersService.verifyAll();
+ });
+});
\ No newline at end of file