mock implementation for new func and passing tests

Signed-off-by: Phil Gore <pgore@ea.com>
This commit is contained in:
Phil Gore
2021-09-14 11:02:13 -05:00
parent ebd2ddf4a2
commit 211698b3f1
3 changed files with 163 additions and 153 deletions
@@ -24,6 +24,10 @@ import {
LdapVendor,
} from './vendors';
export interface SearchCallback {
(entry: SearchEntry): void;
}
/**
* Basic wrapper for the ldapjs library.
*
@@ -110,66 +114,50 @@ export class LdapClient {
}
/**
* Performs an LDAP search operation, creates a generator to limit memory usage
* Performs an LDAP search operation, calls a function on each entry to limit memory usage
*
* @param dn The fully qualified base DN to search within
* @param options The search options
* @param f The callback to call on each search entry
*/
*searchStreaming(
async searchStreaming(
dn: string,
options: SearchOptions,
): IterableIterator<SearchEntry> {
let queueSize = 25;
if (options) {
if (
options.paged &&
typeof options.paged === 'object' &&
typeof options.paged.pageSize === 'number' &&
options.paged.pageSize > 0
) {
queueSize = options.paged.pageSize;
}
}
const queue: SearchEntry[] = new Array(queueSize);
let done = false;
f: SearchCallback,
): Promise<void> {
try {
this.client.search(dn, options, (err, res) => {
if (err) {
throw new Error(errorString(err));
}
res.on('searchReference', () => {
throw new Error('Unable to handle referral');
});
res.on('searchEntry', entry => {
queue.push(entry);
});
res.on('error', e => {
throw new Error(errorString(e));
});
res.on('end', r => {
if (!r) {
throw new Error('Null response');
} else if (r.status !== 0) {
throw new Error(`Got status ${r.status}: ${r.errorMessage}`);
} else {
done = true;
return await new Promise<void>((resolve, reject) => {
this.client.search(dn, options, (err, res) => {
if (err) {
reject(new Error(errorString(err)));
}
res.on('searchReference', () => {
reject(new Error('Unable to handle referral'));
});
res.on('searchEntry', async entry => {
await f(entry);
});
res.on('error', e => {
reject(new Error(errorString(e)));
});
res.on('end', r => {
if (!r) {
throw new Error('Null response');
} else if (r.status !== 0) {
throw new Error(`Got status ${r.status}: ${r.errorMessage}`);
} else {
resolve();
}
});
});
});
} catch (e) {
throw new Error(`LDAP search at DN "${dn}" failed, ${e.message}`);
}
while (!done && queue.length > 0) {
const res = queue.pop();
if (!res) {
continue;
}
yield res;
}
}
/**
@@ -67,7 +67,7 @@ function searchEntry(
describe('readLdapUsers', () => {
const client: jest.Mocked<LdapClient> = {
search: jest.fn(),
searchStreaming: jest.fn(),
getVendor: jest.fn(),
} as any;
@@ -75,18 +75,25 @@ describe('readLdapUsers', () => {
it('transfers all attributes from a default ldap vendor', async () => {
client.getVendor.mockResolvedValue(DefaultLdapVendor);
client.search.mockResolvedValue([
searchEntry({
uid: ['uid-value'],
description: ['description-value'],
cn: ['cn-value'],
mail: ['mail-value'],
avatarUrl: ['avatarUrl-value'],
memberOf: ['x', 'y', 'z'],
entryDN: ['dn-value'],
entryUUID: ['uuid-value'],
}),
]);
client.searchStreaming.mockImplementation(
async (_dn, _opts, fn): Promise<void> => {
return await new Promise<void>((resolve, _reject) => {
fn(
searchEntry({
uid: ['uid-value'],
description: ['description-value'],
cn: ['cn-value'],
mail: ['mail-value'],
avatarUrl: ['avatarUrl-value'],
memberOf: ['x', 'y', 'z'],
entryDN: ['dn-value'],
entryUUID: ['uuid-value'],
}),
);
resolve();
});
},
);
const config: UserConfig = {
dn: 'ddd',
options: {},
@@ -129,37 +136,45 @@ describe('readLdapUsers', () => {
it('transfers all attributes from Microsoft Active Directory', async () => {
client.getVendor.mockResolvedValue(ActiveDirectoryVendor);
client.search.mockResolvedValue([
searchEntry({
uid: ['uid-value'],
description: ['description-value'],
cn: ['cn-value'],
mail: ['mail-value'],
avatarUrl: ['avatarUrl-value'],
memberOf: ['x', 'y', 'z'],
distinguishedName: ['dn-value'],
objectGUID: [
Buffer.from([
68,
2,
125,
190,
209,
0,
94,
73,
133,
33,
230,
174,
234,
195,
160,
152,
]),
],
}),
]);
client.searchStreaming.mockImplementation(
async (_dn, _opts, fn): Promise<void> => {
return await new Promise<void>((resolve, _reject) => {
fn(
searchEntry({
uid: ['uid-value'],
description: ['description-value'],
cn: ['cn-value'],
mail: ['mail-value'],
avatarUrl: ['avatarUrl-value'],
memberOf: ['x', 'y', 'z'],
distinguishedName: ['dn-value'],
objectGUID: [
Buffer.from([
68,
2,
125,
190,
209,
0,
94,
73,
133,
33,
230,
174,
234,
195,
160,
152,
]),
],
}),
);
resolve();
});
},
);
const config: UserConfig = {
dn: 'ddd',
options: {},
@@ -203,7 +218,7 @@ describe('readLdapUsers', () => {
describe('readLdapGroups', () => {
const client: jest.Mocked<LdapClient> = {
search: jest.fn(),
searchStreaming: jest.fn(),
getVendor: jest.fn(),
} as any;
@@ -211,19 +226,26 @@ describe('readLdapGroups', () => {
it('transfers all attributes from a default ldap vendor', async () => {
client.getVendor.mockResolvedValue(DefaultLdapVendor);
client.search.mockResolvedValue([
searchEntry({
cn: ['cn-value'],
description: ['description-value'],
tt: ['type-value'],
mail: ['mail-value'],
avatarUrl: ['avatarUrl-value'],
memberOf: ['x', 'y', 'z'],
member: ['e', 'f', 'g'],
entryDN: ['dn-value'],
entryUUID: ['uuid-value'],
}),
]);
client.searchStreaming.mockImplementation(
async (_dn, _opts, fn): Promise<void> => {
return await new Promise<void>((resolve, _reject) => {
fn(
searchEntry({
cn: ['cn-value'],
description: ['description-value'],
tt: ['type-value'],
mail: ['mail-value'],
avatarUrl: ['avatarUrl-value'],
memberOf: ['x', 'y', 'z'],
member: ['e', 'f', 'g'],
entryDN: ['dn-value'],
entryUUID: ['uuid-value'],
}),
);
resolve();
});
},
);
const config: GroupConfig = {
dn: 'ddd',
options: {},
@@ -274,38 +296,45 @@ describe('readLdapGroups', () => {
});
it('transfers all attributes from Microsoft Active Directory', async () => {
client.getVendor.mockResolvedValue(ActiveDirectoryVendor);
client.search.mockResolvedValue([
searchEntry({
cn: ['cn-value'],
description: ['description-value'],
tt: ['type-value'],
mail: ['mail-value'],
avatarUrl: ['avatarUrl-value'],
memberOf: ['x', 'y', 'z'],
member: ['e', 'f', 'g'],
distinguishedName: ['dn-value'],
objectGUID: [
Buffer.from([
68,
2,
125,
190,
209,
0,
94,
73,
133,
33,
230,
174,
234,
195,
160,
152,
]),
],
}),
]);
client.searchStreaming.mockImplementation(
async (_dn, _opts, fn): Promise<void> => {
return await new Promise<void>((resolve, _reject) => {
fn(
searchEntry({
cn: ['cn-value'],
description: ['description-value'],
tt: ['type-value'],
mail: ['mail-value'],
avatarUrl: ['avatarUrl-value'],
memberOf: ['x', 'y', 'z'],
member: ['e', 'f', 'g'],
distinguishedName: ['dn-value'],
objectGUID: [
Buffer.from([
68,
2,
125,
190,
209,
0,
94,
73,
133,
33,
230,
174,
234,
195,
160,
152,
]),
],
}),
);
resolve();
});
},
);
const config: GroupConfig = {
dn: 'ddd',
options: {},
@@ -107,24 +107,19 @@ export async function readLdapUsers(
const transformer = opts?.transformer ?? defaultUserTransformer;
const entries = client.searchStreaming(dn, options);
for (const entry of entries) {
if (!entry) {
continue;
}
const entity = await transformer(vendor, config, entry);
await client.searchStreaming(dn, options, async user => {
const entity = await transformer(vendor, config, user);
if (!entity) {
continue;
return;
}
mapReferencesAttr(entry, vendor, map.memberOf, (myDn, vs) => {
mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => {
ensureItems(userMemberOf, myDn, vs);
});
entities.push(entity);
}
});
return { users: entities, userMemberOf };
}
@@ -213,17 +208,15 @@ export async function readLdapGroups(
const transformer = opts?.transformer ?? defaultGroupTransformer;
const entries = client.searchStreaming(dn, options);
for (const entry of entries) {
await client.searchStreaming(dn, options, async entry => {
if (!entry) {
continue;
return;
}
const entity = await transformer(vendor, config, entry);
if (!entity) {
continue;
return;
}
mapReferencesAttr(entry, vendor, map.memberOf, (myDn, vs) => {
@@ -234,7 +227,7 @@ export async function readLdapGroups(
});
groups.push(entity);
}
});
return {
groups,