Skip to content

Commit 2f58b5f

Browse files
authored
Add unx group recursively based on FA (#424)
* Add unxGroups at fa creation and migration logic * Format fa * Add tests
1 parent 8d66612 commit 2f58b5f

8 files changed

+353
-17
lines changed

config/be/datasource.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
"password": "",
88
"database": "scilog",
99
"useNewUrlParser": true,
10-
"useUnifiedTopology": true
10+
"useUnifiedTopology": true,
11+
"allowExtendedOperators": true
1112
}

config/be/functionalAccounts.json

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
[{
1+
[
2+
{
23
"username": "scilog-admin",
34
"firstName": "Admin",
45
"lastName": "Scilog",
56
"password": "scilog@scilog",
67
"email": "scilog@scilog",
78
"roles": [
8-
"admin",
9-
"any-authenticated-user"
9+
"admin",
10+
"any-authenticated-user"
1011
]
11-
}]
12+
}
13+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright IBM Corp. 2019,2020. All Rights Reserved.
2+
// Node module: loopback4-example-shopping
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {Client, expect, sinon} from '@loopback/testlab';
7+
import {Suite} from 'mocha';
8+
import {SciLogDbApplication} from '../..';
9+
import {clearDatabase, createUserToken, setupApplication} from './test-helper';
10+
import {BasesnippetRepository} from '../../repositories';
11+
import fs from 'fs';
12+
import {Basesnippet, Location, Logbook, Paragraph} from '../../models';
13+
import {CreateFunctionalAccountsObserver} from '../../observers';
14+
15+
class TestFunctionalAccountsApp extends SciLogDbApplication {
16+
locations = ['location1', 'location2'];
17+
idsLocation: Record<string, string>;
18+
19+
constructor() {
20+
super(SciLogDbApplication);
21+
this.idsLocation = {};
22+
}
23+
24+
async createLocationSnippets(location: string) {
25+
const locationId = await this.createLogbook(location, {
26+
snippetType: 'location',
27+
location: location,
28+
});
29+
const acls = await this.computeAcls();
30+
const snippetId = await this.createLogbook(location, {
31+
snippetType: 'logbook',
32+
location: locationId,
33+
...acls,
34+
});
35+
await this.createLogbook(location, {
36+
snippetType: 'paragraph',
37+
parentId: snippetId,
38+
...acls,
39+
});
40+
}
41+
42+
private async computeAcls() {
43+
const basesnippetRepo = await this.getRepository(BasesnippetRepository);
44+
const acls = basesnippetRepo.acls.reduce(
45+
(currentValue: Record<string, ['acl']>, previousValue: string) => (
46+
(currentValue[previousValue] = ['acl']), currentValue
47+
),
48+
{},
49+
);
50+
return acls;
51+
}
52+
53+
private async createLogbook(
54+
location: string,
55+
snippetArgs: Partial<Location | Logbook | Paragraph>,
56+
) {
57+
const basesnippetRepo = await this.getRepository(BasesnippetRepository);
58+
59+
const snippet = await basesnippetRepo.create(snippetArgs, {
60+
currentUser: {roles: ['admin']},
61+
});
62+
this.idsLocation[snippet.id] = location;
63+
return snippet.id;
64+
}
65+
66+
async migrateSchema() {
67+
for (const location of this.locations) {
68+
await this.createLocationSnippets(location);
69+
}
70+
}
71+
}
72+
73+
describe('CreateFunctionalAccountsObserver', function (this: Suite) {
74+
this.timeout(5000);
75+
let app: TestFunctionalAccountsApp;
76+
let client: Client;
77+
const filePath = './functionalAccounts.json';
78+
const bkFilePath = `${filePath}.bk`;
79+
let token: string;
80+
const sandbox = sinon.createSandbox();
81+
82+
function backupFunctionalAccountsFile() {
83+
if (!fs.existsSync(filePath)) return;
84+
const functionalAccounts = fs.readFileSync(filePath, 'utf8');
85+
fs.writeFileSync(
86+
bkFilePath,
87+
JSON.stringify(JSON.parse(functionalAccounts), null, 2),
88+
'utf8',
89+
);
90+
}
91+
92+
function restoreFunctionalAccounts() {
93+
if (!fs.existsSync(bkFilePath)) return;
94+
fs.copyFileSync(bkFilePath, filePath);
95+
fs.unlinkSync(bkFilePath);
96+
}
97+
98+
function createFunctionalAccountsFile() {
99+
backupFunctionalAccountsFile();
100+
const functionalAccounts = [
101+
{
102+
username: 'account1',
103+
firstName: 'account1',
104+
lastName: 'account1',
105+
password: 'account1@account1',
106+
email: 'account1@account1',
107+
roles: ['admin', 'any-authenticated-user'],
108+
location: 'location1',
109+
},
110+
{
111+
username: 'account2',
112+
firstName: 'account2',
113+
lastName: 'account2',
114+
password: 'account2@account2',
115+
email: 'account2@account2',
116+
roles: ['p123', 'any-authenticated-user'],
117+
unxGroup: 'unxGroup2',
118+
location: 'location2',
119+
},
120+
];
121+
fs.writeFileSync(
122+
filePath,
123+
JSON.stringify(functionalAccounts, null, 2),
124+
'utf8',
125+
);
126+
}
127+
128+
before('setupApplication', async () => {
129+
createFunctionalAccountsFile();
130+
({app, client} = await setupApplication<TestFunctionalAccountsApp>(
131+
{databaseSeeding: true},
132+
TestFunctionalAccountsApp,
133+
));
134+
restoreFunctionalAccounts();
135+
token = await createUserToken(app, client, ['admin']);
136+
});
137+
138+
after(async () => {
139+
await clearDatabase(app);
140+
if (app != null) await app.stop();
141+
});
142+
143+
afterEach(() => {
144+
sandbox.restore();
145+
});
146+
147+
it('addAclsToExistingSnippetsFromAccount', async () => {
148+
await client
149+
.get('/basesnippets')
150+
.set('Authorization', 'Bearer ' + token)
151+
.expect(200)
152+
.then(result => {
153+
result.body.forEach((snippet: Basesnippet) => {
154+
if (
155+
snippet.snippetType === 'location' &&
156+
app.idsLocation[snippet.id] === 'location1'
157+
)
158+
expect(snippet.updateACL).to.eql(undefined);
159+
else if (
160+
snippet.snippetType === 'location' &&
161+
app.idsLocation[snippet.id] === 'location2'
162+
)
163+
expect(snippet.updateACL).to.eql(['unxGroup2']);
164+
else if (app.idsLocation[snippet.id] === 'location1') {
165+
expect(snippet.createACL).to.eql(['acl']);
166+
expect(snippet.readACL).to.eql(['acl']);
167+
expect(snippet.shareACL).to.eql(['acl']);
168+
expect(snippet.updateACL).to.eql(['acl']);
169+
expect(snippet.deleteACL).to.eql(['acl']);
170+
expect(snippet.adminACL).to.eql(['acl']);
171+
} else if (app.idsLocation[snippet.id] === 'location2') {
172+
expect(snippet.createACL).to.eql(['acl', 'unxGroup2']);
173+
expect(snippet.readACL).to.eql(['acl', 'unxGroup2']);
174+
expect(snippet.shareACL).to.eql(['acl', 'unxGroup2']);
175+
expect(snippet.updateACL).to.eql(['acl', 'unxGroup2']);
176+
expect(snippet.deleteACL).to.eql(['acl', 'unxGroup2']);
177+
expect(snippet.adminACL).to.eql(['acl', 'unxGroup2']);
178+
}
179+
});
180+
});
181+
});
182+
183+
it('migrateUnxGroup', async () => {
184+
const createFunctionalAccountsObserver: CreateFunctionalAccountsObserver =
185+
await app.get('lifeCycleObservers.CreateFunctionalAccountsObserver');
186+
const addAclsToExistingSnippetsFromAccountSpy = sandbox.spy(
187+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
188+
createFunctionalAccountsObserver as any,
189+
'addAclsToExistingSnippetsFromAccount',
190+
);
191+
await createFunctionalAccountsObserver.migrateUnxGroup();
192+
expect(addAclsToExistingSnippetsFromAccountSpy.callCount).to.eql(1);
193+
expect(addAclsToExistingSnippetsFromAccountSpy.args[0][0]).to.match({
194+
username: 'account2',
195+
firstName: 'account2',
196+
lastName: 'account2',
197+
email: 'account2@account2',
198+
unxGroup: 'unxGroup2',
199+
location: 'location2',
200+
});
201+
});
202+
});

sci-log-db/src/__tests__/acceptance/test-helper.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import {BasesnippetRepository, TaskRepository} from '../../repositories';
1313
import {FileRepository} from '../../repositories/file.repository';
1414
import {JobRepository} from '../../repositories/job.repository';
1515

16-
export interface AppWithClient {
17-
app: SciLogDbApplication;
16+
export interface AppWithClient<T> {
17+
app: T;
1818
client: Client;
1919
}
2020

@@ -46,16 +46,17 @@ export const oidcOptions = {
4646
postLogoutRedirectUri: 'aRedirectUrl',
4747
};
4848

49-
export async function setupApplication(
49+
export async function setupApplication<T extends SciLogDbApplication>(
5050
options: ApplicationConfig = {},
51-
): Promise<AppWithClient> {
52-
const app = new SciLogDbApplication({
51+
appClass = SciLogDbApplication,
52+
): Promise<AppWithClient<T>> {
53+
const app = new appClass({
5354
rest: givenHttpServerConfig(),
54-
databaseSeeding: false,
5555
...options,
56-
});
56+
}) as T;
5757
await app.boot();
5858
app.dataSource(testdb);
59+
if (!options.dirtyDb) await clearDatabase(app);
5960
await app.start();
6061
const client = createRestAppClient(app);
6162
return {app, client};

sci-log-db/src/__tests__/testdb.datasource.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export const testdb: juggler.DataSource = new juggler.DataSource({
1111
database: 'testdb',
1212
useNewUrlParser: true,
1313
useUnifiedTopology: true,
14+
allowExtendedOperators: true,
1415
});

sci-log-db/src/application.ts

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {OIDCAuthentication} from './authentication-strategies';
5555
import {UserServiceBindings} from './keys';
5656

5757
import {ExpressRequestHandlersProvider} from './express-handlers/middleware-sequence';
58+
import {CreateFunctionalAccountsObserver} from './observers';
5859

5960
export {ApplicationConfig};
6061

@@ -232,6 +233,10 @@ export class SciLogDbApplication extends BootMixin(
232233
const paragraphRepo = await this.getRepository(ParagraphRepository);
233234
await paragraphRepo.migrateHtmlTexcontent();
234235

236+
const createFunctionalAccountsObserver: CreateFunctionalAccountsObserver =
237+
await this.get('lifeCycleObservers.CreateFunctionalAccountsObserver');
238+
await createFunctionalAccountsObserver.migrateUnxGroup();
239+
235240
await super.migrateSchema(options);
236241

237242
// TODO adjust to SciLog case Delete existing shopping carts

0 commit comments

Comments
 (0)