@stamhoofd/backend 2.45.0 → 2.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +2 -0
- package/package.json +10 -10
- package/src/email-recipient-loaders/orders.ts +60 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +19 -1
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +3 -35
- package/src/endpoints/global/members/GetMembersEndpoint.ts +17 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +32 -9
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +14 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +13 -30
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +34 -2
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +2 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +4 -12
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +10 -30
- package/src/excel-loaders/organizations.ts +47 -0
- package/src/helpers/AuthenticatedStructures.ts +6 -2
- package/src/helpers/ModelHelper.ts +28 -0
- package/src/helpers/TagHelper.test.ts +373 -0
- package/src/helpers/TagHelper.ts +170 -0
- package/src/seeds/1729253172-update-orders.ts +28 -0
- package/src/sql-filters/members.ts +54 -19
- package/src/sql-filters/orders.ts +42 -7
- package/src/sql-filters/organizations.ts +4 -0
- package/src/sql-filters/payments.ts +1 -0
- package/src/sql-filters/registrations.ts +3 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
|
-
import { assertSort, CountFilteredRequest, getSortFilter, LimitedFilteredRequest, PaginatedResponse,
|
|
3
|
+
import { assertSort, CountFilteredRequest, getSortFilter, LimitedFilteredRequest, PaginatedResponse, PrivateOrder, StamhoofdFilter } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
|
-
import { Order
|
|
5
|
+
import { Order } from '@stamhoofd/models';
|
|
6
6
|
import { compileToSQLFilter, compileToSQLSorter, SQL, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
|
|
7
7
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
8
8
|
import { Context } from '../../../../helpers/Context';
|
|
@@ -10,7 +10,7 @@ import { LimitedFilteredRequestHelper } from '../../../../helpers/LimitedFiltere
|
|
|
10
10
|
import { orderFilterCompilers } from '../../../../sql-filters/orders';
|
|
11
11
|
import { orderSorters } from '../../../../sql-sorters/orders';
|
|
12
12
|
|
|
13
|
-
type Params =
|
|
13
|
+
type Params = Record<string, never>;
|
|
14
14
|
type Query = LimitedFilteredRequest;
|
|
15
15
|
type Body = undefined;
|
|
16
16
|
type ResponseBody = PaginatedResponse<PrivateOrder[], LimitedFilteredRequest>;
|
|
@@ -26,7 +26,7 @@ export class GetWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
26
26
|
return [false];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const params = Endpoint.parseParameters(request.url, '/webshop
|
|
29
|
+
const params = Endpoint.parseParameters(request.url, '/webshop/orders', {});
|
|
30
30
|
|
|
31
31
|
if (params) {
|
|
32
32
|
return [true, params as Params];
|
|
@@ -34,37 +34,24 @@ export class GetWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
34
34
|
return [false];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
static buildQuery(
|
|
37
|
+
static buildQuery(q: CountFilteredRequest | LimitedFilteredRequest) {
|
|
38
38
|
// todo: filter userId???
|
|
39
39
|
const organization = Context.organization!;
|
|
40
40
|
|
|
41
|
-
if (!webshopId) {
|
|
42
|
-
// todo
|
|
43
|
-
throw new Error();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
41
|
const ordersTable: string = Order.table;
|
|
47
42
|
|
|
48
43
|
const query = SQL
|
|
49
44
|
.select(SQL.wildcard(ordersTable))
|
|
50
45
|
.from(SQL.table(ordersTable))
|
|
51
|
-
// todo: extra check on webshopId to prevent all orders are returned if webshopId is null?
|
|
52
|
-
.where('webshopId', webshopId)
|
|
53
46
|
.where(compileToSQLFilter({
|
|
54
|
-
|
|
55
|
-
{
|
|
56
|
-
organizationId: organization.id,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
organizationId: null,
|
|
60
|
-
},
|
|
61
|
-
],
|
|
47
|
+
organizationId: organization.id,
|
|
62
48
|
}, filterCompilers));
|
|
63
49
|
|
|
64
50
|
if (q.filter) {
|
|
65
51
|
query.where(compileToSQLFilter(q.filter, filterCompilers));
|
|
66
52
|
}
|
|
67
53
|
|
|
54
|
+
// todo: use same logic as frontend
|
|
68
55
|
if (q.search) {
|
|
69
56
|
let searchFilter: StamhoofdFilter | null = null;
|
|
70
57
|
|
|
@@ -93,8 +80,8 @@ export class GetWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
93
80
|
return query;
|
|
94
81
|
}
|
|
95
82
|
|
|
96
|
-
static async buildData(
|
|
97
|
-
const query = this.buildQuery(
|
|
83
|
+
static async buildData(requestQuery: LimitedFilteredRequest) {
|
|
84
|
+
const query = this.buildQuery(requestQuery);
|
|
98
85
|
const data = await query.fetch();
|
|
99
86
|
|
|
100
87
|
const orders: Order[] = Order.fromRows(data, Order.table);
|
|
@@ -139,15 +126,8 @@ export class GetWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
139
126
|
maxLimit: Context.auth.hasSomePlatformAccess() ? 1000 : 100,
|
|
140
127
|
});
|
|
141
128
|
|
|
142
|
-
const webshopId = request.params.id;
|
|
143
|
-
|
|
144
|
-
const webshop = await Webshop.getByID(webshopId);
|
|
145
|
-
if (!webshop || !await Context.auth.canAccessWebshop(webshop, PermissionLevel.Read)) {
|
|
146
|
-
throw Context.auth.notFoundOrNoAccess('Je hebt geen toegang tot de bestellingen van deze webshop');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
129
|
return new Response(
|
|
150
|
-
await GetWebshopOrdersEndpoint.buildData(
|
|
130
|
+
await GetWebshopOrdersEndpoint.buildData(request.query),
|
|
151
131
|
);
|
|
152
132
|
|
|
153
133
|
/*
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { XlsxTransformerSheet } from '@stamhoofd/excel-writer';
|
|
2
|
+
import { ExcelExportType, LimitedFilteredRequest, Organization as OrganizationStruct } from '@stamhoofd/structures';
|
|
3
|
+
import { GetOrganizationsEndpoint } from '../endpoints/admin/organizations/GetOrganizationsEndpoint';
|
|
4
|
+
import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint';
|
|
5
|
+
|
|
6
|
+
// Assign to a typed variable to assure we have correct type checking in place
|
|
7
|
+
const sheet: XlsxTransformerSheet<OrganizationStruct, OrganizationStruct> = {
|
|
8
|
+
id: 'organizations',
|
|
9
|
+
name: 'Leden',
|
|
10
|
+
columns: [
|
|
11
|
+
{
|
|
12
|
+
id: 'id',
|
|
13
|
+
name: 'ID',
|
|
14
|
+
width: 20,
|
|
15
|
+
getValue: (object: OrganizationStruct) => ({
|
|
16
|
+
value: object.id,
|
|
17
|
+
}),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'uri',
|
|
21
|
+
name: 'Groepsnummer',
|
|
22
|
+
width: 20,
|
|
23
|
+
getValue: (object: OrganizationStruct) => ({
|
|
24
|
+
value: object.uri,
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'name',
|
|
29
|
+
name: 'Naam',
|
|
30
|
+
width: 50,
|
|
31
|
+
getValue: (object: OrganizationStruct) => ({
|
|
32
|
+
value: object.name,
|
|
33
|
+
}),
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
ExportToExcelEndpoint.loaders.set(ExcelExportType.Organizations, {
|
|
39
|
+
fetch: async (query: LimitedFilteredRequest) => {
|
|
40
|
+
const result = await GetOrganizationsEndpoint.buildData(query);
|
|
41
|
+
|
|
42
|
+
return result;
|
|
43
|
+
},
|
|
44
|
+
sheets: [
|
|
45
|
+
sheet,
|
|
46
|
+
],
|
|
47
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
2
|
import { CachedBalance, Event, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, User, Webshop } from '@stamhoofd/models';
|
|
3
|
-
import { AccessRight,
|
|
3
|
+
import { AccessRight, Event as EventStruct, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { Context } from './Context';
|
|
@@ -130,6 +130,10 @@ export class AuthenticatedStructures {
|
|
|
130
130
|
return (await this.organizations([organization]))[0];
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
static webshopPreview(webshop: Webshop) {
|
|
134
|
+
return WebshopPreview.create(webshop);
|
|
135
|
+
}
|
|
136
|
+
|
|
133
137
|
static async organizations(organizations: Organization[]): Promise<OrganizationStruct[]> {
|
|
134
138
|
if (organizations.length === 0) {
|
|
135
139
|
return [];
|
|
@@ -210,7 +214,7 @@ export class AuthenticatedStructures {
|
|
|
210
214
|
|
|
211
215
|
const organizationId = w.organizationId;
|
|
212
216
|
const array = webshopPreviews.get(organizationId);
|
|
213
|
-
const preview =
|
|
217
|
+
const preview = this.webshopPreview(w);
|
|
214
218
|
|
|
215
219
|
if (array) {
|
|
216
220
|
array.push(preview);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Model } from '@simonbackx/simple-database';
|
|
2
|
+
|
|
3
|
+
// todo: move for reuse?
|
|
4
|
+
type KeysMatching<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];
|
|
5
|
+
|
|
6
|
+
export class ModelHelper {
|
|
7
|
+
static async loop<M extends typeof Model>(m: M, idKey: KeysMatching<InstanceType<M>, string> & string, onBatchReceived: (batch: InstanceType<M>[]) => Promise<void>, options: { limit?: number } = {}) {
|
|
8
|
+
let lastId = '';
|
|
9
|
+
const limit = options.limit ?? 10;
|
|
10
|
+
|
|
11
|
+
while (true) {
|
|
12
|
+
const models = await m.where(
|
|
13
|
+
{ [idKey]: { sign: '>', value: lastId } },
|
|
14
|
+
{ limit, sort: [idKey] });
|
|
15
|
+
|
|
16
|
+
if (models.length === 0) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await onBatchReceived(models);
|
|
21
|
+
|
|
22
|
+
lastId
|
|
23
|
+
= models[
|
|
24
|
+
models.length - 1
|
|
25
|
+
][idKey] as string;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { OrganizationTag } from '@stamhoofd/structures';
|
|
2
|
+
import { TagHelper } from './TagHelper';
|
|
3
|
+
|
|
4
|
+
// todo: move tests for methods of shared package to shared package
|
|
5
|
+
describe('TagHelper', () => {
|
|
6
|
+
describe('containsDeep', () => {
|
|
7
|
+
it('should return true if the tag contains the tag to search recursively or false otherwise', () => {
|
|
8
|
+
// arrange
|
|
9
|
+
const tags = new Map<string, OrganizationTag>([
|
|
10
|
+
[
|
|
11
|
+
'id1',
|
|
12
|
+
OrganizationTag.create({
|
|
13
|
+
id: 'id1',
|
|
14
|
+
name: 'tag1',
|
|
15
|
+
childTags: [],
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
[
|
|
19
|
+
'id2',
|
|
20
|
+
OrganizationTag.create({
|
|
21
|
+
id: 'id2',
|
|
22
|
+
name: 'tag2',
|
|
23
|
+
childTags: [],
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
26
|
+
[
|
|
27
|
+
'id3',
|
|
28
|
+
OrganizationTag.create({
|
|
29
|
+
id: 'id3',
|
|
30
|
+
name: 'tag3',
|
|
31
|
+
childTags: ['id2', 'id4'],
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
'id4',
|
|
36
|
+
OrganizationTag.create({
|
|
37
|
+
id: 'id4',
|
|
38
|
+
name: 'tag4',
|
|
39
|
+
childTags: ['id5'],
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
'id5',
|
|
44
|
+
OrganizationTag.create({
|
|
45
|
+
id: 'id5',
|
|
46
|
+
name: 'tag5',
|
|
47
|
+
childTags: [],
|
|
48
|
+
}),
|
|
49
|
+
],
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
// act
|
|
53
|
+
const doesTag3ContainTag4 = TagHelper.containsDeep('id3', 'id4', tags);
|
|
54
|
+
const doesTag3ContainTag5 = TagHelper.containsDeep('id3', 'id5', tags);
|
|
55
|
+
const doesTag3ContainTag1 = TagHelper.containsDeep('id3', 'id1', tags);
|
|
56
|
+
|
|
57
|
+
// assert
|
|
58
|
+
expect(doesTag3ContainTag4).toBe(true);
|
|
59
|
+
expect(doesTag3ContainTag5).toBe(true);
|
|
60
|
+
expect(doesTag3ContainTag1).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('getAllTagsFromHierarchy', () => {
|
|
65
|
+
it('should return array with the tag ids that are known by the platform and add tag ids if the organization has a tag that is a child tag of that tag', () => {
|
|
66
|
+
// arrange
|
|
67
|
+
const originalTagIds: string[] = ['id5', 'id3', 'unknownTagId'];
|
|
68
|
+
const platformTags: OrganizationTag[] = [
|
|
69
|
+
OrganizationTag.create({
|
|
70
|
+
id: 'id0',
|
|
71
|
+
name: 'tag0',
|
|
72
|
+
childTags: ['id7', 'id9'],
|
|
73
|
+
}),
|
|
74
|
+
OrganizationTag.create({
|
|
75
|
+
id: 'id1',
|
|
76
|
+
name: 'tag1',
|
|
77
|
+
childTags: [],
|
|
78
|
+
}),
|
|
79
|
+
OrganizationTag.create({
|
|
80
|
+
id: 'id2',
|
|
81
|
+
name: 'tag2',
|
|
82
|
+
childTags: [],
|
|
83
|
+
}),
|
|
84
|
+
OrganizationTag.create({
|
|
85
|
+
id: 'id3',
|
|
86
|
+
name: 'tag3',
|
|
87
|
+
childTags: [],
|
|
88
|
+
}),
|
|
89
|
+
OrganizationTag.create({
|
|
90
|
+
id: 'id4',
|
|
91
|
+
name: 'tag4',
|
|
92
|
+
childTags: ['id3'],
|
|
93
|
+
}),
|
|
94
|
+
OrganizationTag.create({
|
|
95
|
+
id: 'id5',
|
|
96
|
+
name: 'tag5',
|
|
97
|
+
childTags: [],
|
|
98
|
+
}),
|
|
99
|
+
OrganizationTag.create({
|
|
100
|
+
id: 'id6',
|
|
101
|
+
name: 'tag6',
|
|
102
|
+
childTags: ['id5'],
|
|
103
|
+
}),
|
|
104
|
+
OrganizationTag.create({
|
|
105
|
+
id: 'id7',
|
|
106
|
+
name: 'tag7',
|
|
107
|
+
childTags: ['id6', 'id8', 'id9'],
|
|
108
|
+
}),
|
|
109
|
+
OrganizationTag.create({
|
|
110
|
+
id: 'id8',
|
|
111
|
+
name: 'tag8',
|
|
112
|
+
childTags: [],
|
|
113
|
+
}),
|
|
114
|
+
OrganizationTag.create({
|
|
115
|
+
id: 'id9',
|
|
116
|
+
name: 'tag9',
|
|
117
|
+
childTags: [],
|
|
118
|
+
}),
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
// act
|
|
122
|
+
const result = TagHelper.getAllTagsFromHierarchy(originalTagIds, platformTags);
|
|
123
|
+
|
|
124
|
+
// assert
|
|
125
|
+
expect(result).toHaveLength(6);
|
|
126
|
+
expect(result).toInclude('id5');
|
|
127
|
+
expect(result).toInclude('id3');
|
|
128
|
+
expect(result).toInclude('id4');
|
|
129
|
+
expect(result).toInclude('id6');
|
|
130
|
+
expect(result).toInclude('id7');
|
|
131
|
+
expect(result).toInclude('id0');
|
|
132
|
+
expect(result).not.toInclude('unknownTagId');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('getCleanedUpTags', () => {
|
|
137
|
+
it('should remove child tag ids that do not exist', () => {
|
|
138
|
+
// arrange
|
|
139
|
+
const tag5 = OrganizationTag.create({
|
|
140
|
+
id: 'id5',
|
|
141
|
+
name: 'tag5',
|
|
142
|
+
childTags: ['doesNotExist2'],
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const tag7 = OrganizationTag.create({
|
|
146
|
+
id: 'id7',
|
|
147
|
+
name: 'tag7',
|
|
148
|
+
childTags: ['id6', 'id8', 'id9', 'doesNotExist1'],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const platformTags: OrganizationTag[] = [
|
|
152
|
+
OrganizationTag.create({
|
|
153
|
+
id: 'id0',
|
|
154
|
+
name: 'tag0',
|
|
155
|
+
childTags: ['id7', 'id9'],
|
|
156
|
+
}),
|
|
157
|
+
OrganizationTag.create({
|
|
158
|
+
id: 'id1',
|
|
159
|
+
name: 'tag1',
|
|
160
|
+
childTags: [],
|
|
161
|
+
}),
|
|
162
|
+
OrganizationTag.create({
|
|
163
|
+
id: 'id2',
|
|
164
|
+
name: 'tag2',
|
|
165
|
+
childTags: [],
|
|
166
|
+
}),
|
|
167
|
+
OrganizationTag.create({
|
|
168
|
+
id: 'id3',
|
|
169
|
+
name: 'tag3',
|
|
170
|
+
childTags: [],
|
|
171
|
+
}),
|
|
172
|
+
OrganizationTag.create({
|
|
173
|
+
id: 'id4',
|
|
174
|
+
name: 'tag4',
|
|
175
|
+
childTags: ['id3'],
|
|
176
|
+
}),
|
|
177
|
+
tag5,
|
|
178
|
+
OrganizationTag.create({
|
|
179
|
+
id: 'id6',
|
|
180
|
+
name: 'tag6',
|
|
181
|
+
childTags: ['id5'],
|
|
182
|
+
}),
|
|
183
|
+
tag7,
|
|
184
|
+
OrganizationTag.create({
|
|
185
|
+
id: 'id8',
|
|
186
|
+
name: 'tag8',
|
|
187
|
+
childTags: [],
|
|
188
|
+
}),
|
|
189
|
+
OrganizationTag.create({
|
|
190
|
+
id: 'id9',
|
|
191
|
+
name: 'tag9',
|
|
192
|
+
childTags: [],
|
|
193
|
+
}),
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
// act
|
|
197
|
+
TagHelper.getCleanedUpTags(platformTags);
|
|
198
|
+
|
|
199
|
+
// assert
|
|
200
|
+
expect(tag5.childTags).toHaveLength(0);
|
|
201
|
+
expect(tag7.childTags).toHaveLength(3);
|
|
202
|
+
expect(tag7.childTags).not.toInclude('doesNotExist1');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should return array of tags in the correct order', () => {
|
|
206
|
+
// arrange
|
|
207
|
+
const platformTags: OrganizationTag[] = [
|
|
208
|
+
OrganizationTag.create({
|
|
209
|
+
id: 'id2b1',
|
|
210
|
+
name: 'tag2b1',
|
|
211
|
+
childTags: [],
|
|
212
|
+
}),
|
|
213
|
+
OrganizationTag.create({
|
|
214
|
+
id: 'id1',
|
|
215
|
+
name: 'tag1',
|
|
216
|
+
childTags: [],
|
|
217
|
+
}),
|
|
218
|
+
OrganizationTag.create({
|
|
219
|
+
id: 'id2',
|
|
220
|
+
name: 'tag2',
|
|
221
|
+
childTags: ['id2a', 'id2b'],
|
|
222
|
+
}),
|
|
223
|
+
OrganizationTag.create({
|
|
224
|
+
id: 'id3',
|
|
225
|
+
name: 'tag3',
|
|
226
|
+
childTags: [],
|
|
227
|
+
}),
|
|
228
|
+
OrganizationTag.create({
|
|
229
|
+
id: 'id2a',
|
|
230
|
+
name: 'tag2a',
|
|
231
|
+
childTags: ['id2a1', 'id2a2'],
|
|
232
|
+
}),
|
|
233
|
+
OrganizationTag.create({
|
|
234
|
+
id: 'id2b',
|
|
235
|
+
name: 'tag2b',
|
|
236
|
+
childTags: ['id2b1'],
|
|
237
|
+
}),
|
|
238
|
+
OrganizationTag.create({
|
|
239
|
+
id: 'id2a1',
|
|
240
|
+
name: 'tag2a1',
|
|
241
|
+
childTags: [],
|
|
242
|
+
}),
|
|
243
|
+
OrganizationTag.create({
|
|
244
|
+
id: 'id2a2',
|
|
245
|
+
name: 'tag2a2',
|
|
246
|
+
childTags: [],
|
|
247
|
+
}),
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
// act
|
|
251
|
+
const result = TagHelper.getCleanedUpTags(platformTags);
|
|
252
|
+
|
|
253
|
+
// assert
|
|
254
|
+
expect(result).toHaveLength(8);
|
|
255
|
+
expect(result[0].id).toBe('id1');
|
|
256
|
+
expect(result[1].id).toBe('id2');
|
|
257
|
+
expect(result[2].id).toBe('id2a');
|
|
258
|
+
expect(result[3].id).toBe('id2a1');
|
|
259
|
+
expect(result[4].id).toBe('id2a2');
|
|
260
|
+
expect(result[5].id).toBe('id2b');
|
|
261
|
+
expect(result[6].id).toBe('id2b1');
|
|
262
|
+
expect(result[7].id).toBe('id3');
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('validateTags', () => {
|
|
267
|
+
it('should return false if a tag is a child tag of itself', () => {
|
|
268
|
+
// arrange
|
|
269
|
+
const invalidPlatformTags: OrganizationTag[] = [
|
|
270
|
+
OrganizationTag.create({
|
|
271
|
+
id: 'id1',
|
|
272
|
+
name: 'tag1',
|
|
273
|
+
childTags: ['id1'],
|
|
274
|
+
}),
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
const validPlatformTags: OrganizationTag[] = [
|
|
278
|
+
OrganizationTag.create({
|
|
279
|
+
id: 'id1',
|
|
280
|
+
name: 'tag1',
|
|
281
|
+
childTags: ['id2'],
|
|
282
|
+
}),
|
|
283
|
+
OrganizationTag.create({
|
|
284
|
+
id: 'id2',
|
|
285
|
+
name: 'tag2',
|
|
286
|
+
childTags: [],
|
|
287
|
+
}),
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
// act
|
|
291
|
+
const result1 = TagHelper.validateTags(invalidPlatformTags);
|
|
292
|
+
const result2 = TagHelper.validateTags(validPlatformTags);
|
|
293
|
+
|
|
294
|
+
// assert
|
|
295
|
+
expect(result1).toBeFalse();
|
|
296
|
+
expect(result2).toBeTrue();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should return false if a tag is a child tag of multiple tags', () => {
|
|
300
|
+
// arrange
|
|
301
|
+
const invalidPlatformTags: OrganizationTag[] = [
|
|
302
|
+
OrganizationTag.create({
|
|
303
|
+
id: 'id1',
|
|
304
|
+
name: 'tag1',
|
|
305
|
+
childTags: [],
|
|
306
|
+
}),
|
|
307
|
+
OrganizationTag.create({
|
|
308
|
+
id: 'id2',
|
|
309
|
+
name: 'tag2',
|
|
310
|
+
childTags: ['id1'],
|
|
311
|
+
}),
|
|
312
|
+
OrganizationTag.create({
|
|
313
|
+
id: 'id3',
|
|
314
|
+
name: 'tag3',
|
|
315
|
+
childTags: ['id1'],
|
|
316
|
+
}),
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
const validPlatformTags: OrganizationTag[] = [
|
|
320
|
+
OrganizationTag.create({
|
|
321
|
+
id: 'id1',
|
|
322
|
+
name: 'tag1',
|
|
323
|
+
childTags: [],
|
|
324
|
+
}),
|
|
325
|
+
OrganizationTag.create({
|
|
326
|
+
id: 'id2',
|
|
327
|
+
name: 'tag2',
|
|
328
|
+
childTags: ['id3'],
|
|
329
|
+
}),
|
|
330
|
+
OrganizationTag.create({
|
|
331
|
+
id: 'id3',
|
|
332
|
+
name: 'tag3',
|
|
333
|
+
childTags: ['id1'],
|
|
334
|
+
}),
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
// act
|
|
338
|
+
const result1 = TagHelper.validateTags(invalidPlatformTags);
|
|
339
|
+
const result2 = TagHelper.validateTags(validPlatformTags);
|
|
340
|
+
|
|
341
|
+
// assert
|
|
342
|
+
expect(result1).toBeFalse();
|
|
343
|
+
expect(result2).toBeTrue();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('should return false if the child tags contain an infinite loop', () => {
|
|
347
|
+
// arrange
|
|
348
|
+
const platformTagsWithInfiniteLoop: OrganizationTag[] = [
|
|
349
|
+
OrganizationTag.create({
|
|
350
|
+
id: 'id1',
|
|
351
|
+
name: 'tag1',
|
|
352
|
+
childTags: ['id2'],
|
|
353
|
+
}),
|
|
354
|
+
OrganizationTag.create({
|
|
355
|
+
id: 'id2',
|
|
356
|
+
name: 'tag2',
|
|
357
|
+
childTags: ['id3'],
|
|
358
|
+
}),
|
|
359
|
+
OrganizationTag.create({
|
|
360
|
+
id: 'id3',
|
|
361
|
+
name: 'tag3',
|
|
362
|
+
childTags: ['id1'],
|
|
363
|
+
}),
|
|
364
|
+
];
|
|
365
|
+
|
|
366
|
+
// act
|
|
367
|
+
const result = TagHelper.validateTags(platformTagsWithInfiniteLoop);
|
|
368
|
+
|
|
369
|
+
// assert
|
|
370
|
+
expect(result).toBeFalse();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
});
|