Skip to content

Commit a4d61e0

Browse files
committed
PM-2684: add member-api fallback for invite email resolution
What was broken - Invite email target resolution depended only on Identity API email lookups. - When those lookups failed, existing Topcoder members invited by email were treated as non-members and routed through the registration-template path. Root cause - had no fallback source for unresolved emails and silently returned no user matches. What was changed - Updated to keep Identity API as primary lookup and fallback unresolved emails to Member API email search (). - Mapped Member API records into shape and deduplicated merged results. - Added safe primitive-string normalization helpers used during response parsing. Any added/updated tests - Added with coverage for Identity API success and Member API fallback paths.
1 parent bdbcb10 commit a4d61e0

2 files changed

Lines changed: 335 additions & 53 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { HttpService } from '@nestjs/axios';
2+
import { of, throwError } from 'rxjs';
3+
import { M2MService } from 'src/shared/modules/global/m2m.service';
4+
import { IdentityService } from './identity.service';
5+
6+
jest.mock('src/shared/config/service-endpoints.config', () => ({
7+
SERVICE_ENDPOINTS: {
8+
identityApiUrl: 'https://identity.test',
9+
memberApiUrl: 'https://member.test',
10+
},
11+
}));
12+
13+
describe('IdentityService', () => {
14+
const httpServiceMock = {
15+
get: jest.fn(),
16+
};
17+
18+
const m2mServiceMock = {
19+
getM2MToken: jest.fn().mockResolvedValue('m2m-token'),
20+
};
21+
22+
let service: IdentityService;
23+
24+
beforeEach(() => {
25+
jest.clearAllMocks();
26+
27+
service = new IdentityService(
28+
httpServiceMock as unknown as HttpService,
29+
m2mServiceMock as unknown as M2MService,
30+
);
31+
});
32+
33+
it('returns users from Identity API when lookups succeed', async () => {
34+
httpServiceMock.get.mockImplementation(
35+
(url: string, options: { params?: { filter?: string } }) => {
36+
if (
37+
url === 'https://identity.test/users' &&
38+
options.params?.filter === 'email=member1@topcoder.com'
39+
) {
40+
return of({
41+
data: [
42+
{
43+
id: '1001',
44+
handle: 'member1',
45+
email: 'member1@topcoder.com',
46+
},
47+
],
48+
});
49+
}
50+
51+
if (
52+
url === 'https://identity.test/users' &&
53+
options.params?.filter === 'email=member2@topcoder.com'
54+
) {
55+
return of({
56+
data: [
57+
{
58+
id: '1002',
59+
handle: 'member2',
60+
email: 'member2@topcoder.com',
61+
},
62+
],
63+
});
64+
}
65+
66+
return of({ data: [] });
67+
},
68+
);
69+
70+
const result = await service.lookupMultipleUserEmails([
71+
'member1@topcoder.com',
72+
'member2@topcoder.com',
73+
]);
74+
75+
expect(result).toEqual([
76+
{
77+
id: '1001',
78+
handle: 'member1',
79+
email: 'member1@topcoder.com',
80+
},
81+
{
82+
id: '1002',
83+
handle: 'member2',
84+
email: 'member2@topcoder.com',
85+
},
86+
]);
87+
88+
expect(m2mServiceMock.getM2MToken).toHaveBeenCalledTimes(1);
89+
});
90+
91+
it('falls back to Member API for unresolved emails', async () => {
92+
httpServiceMock.get.mockImplementation(
93+
(url: string, options: { params?: { email?: string } }) => {
94+
if (url === 'https://identity.test/users') {
95+
return throwError(() => new Error('identity lookup failed'));
96+
}
97+
98+
if (
99+
url === 'https://member.test' &&
100+
options.params?.email === 'existing@topcoder.com'
101+
) {
102+
return of({
103+
data: [
104+
{
105+
userId: 2001,
106+
handle: 'existing',
107+
email: 'existing@topcoder.com',
108+
},
109+
],
110+
});
111+
}
112+
113+
if (
114+
url === 'https://member.test' &&
115+
options.params?.email === 'newuser@topcoder.com'
116+
) {
117+
return of({ data: [] });
118+
}
119+
120+
return of({ data: [] });
121+
},
122+
);
123+
124+
const result = await service.lookupMultipleUserEmails([
125+
'existing@topcoder.com',
126+
'newuser@topcoder.com',
127+
]);
128+
129+
expect(result).toEqual([
130+
{
131+
id: '2001',
132+
handle: 'existing',
133+
email: 'existing@topcoder.com',
134+
},
135+
]);
136+
137+
expect(m2mServiceMock.getM2MToken).toHaveBeenCalledTimes(1);
138+
});
139+
});

0 commit comments

Comments
 (0)