Skip to content

Commit 79e2c75

Browse files
committed
test: update tests with new high value cases
Signed-off-by: Yeganathan S <63534555+skwowet@users.noreply.github.com>
1 parent b15d1ee commit 79e2c75

1 file changed

Lines changed: 294 additions & 18 deletions

File tree

  • services/libs/data-access-layer/src/member-organization-affiliation

services/libs/data-access-layer/src/member-organization-affiliation/index.test.ts

Lines changed: 294 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import {
2626
} from '@crowd/types'
2727

2828
import { queryActivityRelations } from '../activityRelations'
29+
import { deleteMemberSegmentAffiliations } from '../member_segment_affiliations'
30+
import { deleteMemberOrganizations } from '../members/organizations'
31+
import { insertOrganizationSegments } from '../organizations/segments'
2932

3033
import {
3134
prepareMemberOrganizationAffiliationTimeline,
@@ -231,18 +234,37 @@ describe('prepareMemberOrganizationAffiliationTimeline', () => {
231234
])
232235

233236
const timeline = await prepareMemberOrganizationAffiliationTimeline(qx, member.id)
234-
const orgs = baseItems(timeline).map((item) => item.organizationId)
235-
236-
expect(orgs[0]).toBe(undated.id)
237-
expect(orgs).toContain(early.id)
238-
expect(orgs).toContain(later.id)
239-
// Mid-career hole between early end and later start must also be undated.
240-
const gapIdx = orgs.findIndex((id, i) => id === undated.id && i > 0 && orgs[i - 1] === early.id)
241-
expect(gapIdx).toBeGreaterThan(0)
242-
expect(orgs[gapIdx + 1]).toBe(later.id)
237+
238+
expectItems(baseItems(timeline), [
239+
{
240+
organizationId: undated.id,
241+
dateStart: EPOCH,
242+
dateEnd: '2017-06-30T00:00:00.000Z',
243+
},
244+
{
245+
organizationId: early.id,
246+
dateStart: '2017-07-01T00:00:00.000Z',
247+
dateEnd: '2020-01-01T00:00:00.000Z',
248+
},
249+
{
250+
organizationId: undated.id,
251+
dateStart: '2020-01-02T00:00:00.000Z',
252+
dateEnd: '2020-02-29T00:00:00.000Z',
253+
},
254+
{
255+
organizationId: later.id,
256+
dateStart: '2020-03-01T00:00:00.000Z',
257+
dateEnd: '2021-08-01T00:00:00.000Z',
258+
},
259+
{
260+
organizationId: undated.id,
261+
dateStart: '2021-08-02T00:00:00.000Z',
262+
dateEnd: null,
263+
},
264+
])
243265
})
244266

245-
test('primary open-ended employer wins over overlapping blocked stints', async ({ qx }) => {
267+
test('allowAffiliation false drops overlapping stints from the timeline', async ({ qx }) => {
246268
const [employer, internship, foundation, undated] = await createOrganizations(
247269
qx,
248270
withOrganizationDefaults([
@@ -290,12 +312,6 @@ describe('prepareMemberOrganizationAffiliationTimeline', () => {
290312
])
291313

292314
await createMemberOrganizationAffiliationOverrides(qx, [
293-
{
294-
memberId: member.id,
295-
memberOrganizationId: mos[0].id,
296-
allowAffiliation: true,
297-
isPrimaryWorkExperience: true,
298-
},
299315
{
300316
memberId: member.id,
301317
memberOrganizationId: mos[1].id,
@@ -390,12 +406,15 @@ describe('prepareMemberOrganizationAffiliationTimeline', () => {
390406
expect(baseItems(timeline).every((item) => item.organizationId !== orgA.id)).toBe(true)
391407
})
392408

393-
test('blacklisted titles like Investor and Mentor are excluded', async ({ qx }) => {
394-
const [investorOrg, mentorOrg, engOrg] = await createOrganizations(
409+
test('blacklisted titles like Investor, Mentor, and Board Member are excluded', async ({
410+
qx,
411+
}) => {
412+
const [investorOrg, mentorOrg, boardOrg, engOrg] = await createOrganizations(
395413
qx,
396414
withOrganizationDefaults([
397415
{ displayName: 'Various Startups' },
398416
{ displayName: 'Google' },
417+
{ displayName: 'Acme Board' },
399418
{ displayName: 'Red Hat' },
400419
]),
401420
)
@@ -418,6 +437,14 @@ describe('prepareMemberOrganizationAffiliationTimeline', () => {
418437
title: 'Mentor',
419438
source: OrganizationSource.ENRICHMENT_CRUSTDATA,
420439
},
440+
{
441+
memberId: member.id,
442+
organizationId: boardOrg.id,
443+
dateStart: '2018-01-01T00:00:00.000Z',
444+
dateEnd: '2020-01-01T00:00:00.000Z',
445+
title: 'Board Member',
446+
source: OrganizationSource.ENRICHMENT_CRUSTDATA,
447+
},
421448
{
422449
memberId: member.id,
423450
organizationId: engOrg.id,
@@ -433,9 +460,81 @@ describe('prepareMemberOrganizationAffiliationTimeline', () => {
433460

434461
expect(orgIds).not.toContain(investorOrg.id)
435462
expect(orgIds).not.toContain(mentorOrg.id)
463+
expect(orgIds).not.toContain(boardOrg.id)
436464
expect(orgIds).toContain(engOrg.id)
437465
})
438466

467+
test('soft-deleted MO is ignored', async ({ qx }) => {
468+
const [activeOrg, deletedOrg] = await createOrganizations(
469+
qx,
470+
withOrganizationDefaults([{ displayName: 'Red Hat' }, { displayName: 'Gone Co' }]),
471+
)
472+
const [member] = await createMembers(qx, withMemberDefaults([{}]))
473+
474+
const [, deletedMo] = await createMemberOrganizations(qx, [
475+
{
476+
memberId: member.id,
477+
organizationId: activeOrg.id,
478+
dateStart: '2022-01-01T00:00:00.000Z',
479+
dateEnd: null,
480+
source: OrganizationSource.UI,
481+
},
482+
{
483+
memberId: member.id,
484+
organizationId: deletedOrg.id,
485+
dateStart: '2020-01-01T00:00:00.000Z',
486+
dateEnd: '2021-01-01T00:00:00.000Z',
487+
source: OrganizationSource.UI,
488+
},
489+
])
490+
491+
await deleteMemberOrganizations(qx, member.id, [deletedMo.id])
492+
493+
const timeline = await prepareMemberOrganizationAffiliationTimeline(qx, member.id)
494+
const orgIds = baseItems(timeline).map((item) => item.organizationId)
495+
496+
expect(orgIds).toContain(activeOrg.id)
497+
expect(orgIds).not.toContain(deletedOrg.id)
498+
})
499+
500+
test('soft-deleted MSA is ignored', async ({ qx }) => {
501+
const [employer, sponsor] = await createOrganizations(
502+
qx,
503+
withOrganizationDefaults([{ displayName: 'Akuity Inc' }, { displayName: 'Red Hat' }]),
504+
)
505+
const [member] = await createMembers(qx, withMemberDefaults([{}]))
506+
const { kubernetes } = await createLeafSegments(qx)
507+
508+
await createMemberOrganizations(qx, [
509+
{
510+
memberId: member.id,
511+
organizationId: employer.id,
512+
dateStart: '2021-08-01T00:00:00.000Z',
513+
dateEnd: null,
514+
source: OrganizationSource.UI,
515+
},
516+
])
517+
518+
const [msa] = await createMemberSegmentAffiliations(qx, [
519+
{
520+
memberId: member.id,
521+
segmentId: kubernetes.id,
522+
organizationId: sponsor.id,
523+
dateStart: null,
524+
dateEnd: null,
525+
},
526+
])
527+
528+
await deleteMemberSegmentAffiliations(qx, { ids: [msa.id] })
529+
530+
const timeline = await prepareMemberOrganizationAffiliationTimeline(qx, member.id)
531+
532+
expect(manualItems(timeline)).toHaveLength(0)
533+
expect(baseItems(timeline).every((item) => item.skipManualAffiliationSegments === false)).toBe(
534+
true,
535+
)
536+
})
537+
439538
test('undated MSA is a 1970 catch-all; base timeline skips that segment', async ({ qx }) => {
440539
const [employer, sponsor] = await createOrganizations(
441540
qx,
@@ -475,6 +574,28 @@ describe('prepareMemberOrganizationAffiliationTimeline', () => {
475574
)
476575
})
477576

577+
test('without MSA, base timeline does not skip manual affiliation segments', async ({ qx }) => {
578+
const [org] = await createOrganizations(qx, withOrganizationDefaults([{ displayName: 'Meta' }]))
579+
const [member] = await createMembers(qx, withMemberDefaults([{}]))
580+
581+
await createMemberOrganizations(qx, [
582+
{
583+
memberId: member.id,
584+
organizationId: org.id,
585+
dateStart: '2019-06-01T00:00:00.000Z',
586+
dateEnd: null,
587+
source: OrganizationSource.UI,
588+
},
589+
])
590+
591+
const timeline = await prepareMemberOrganizationAffiliationTimeline(qx, member.id)
592+
593+
expect(manualItems(timeline)).toHaveLength(0)
594+
expect(baseItems(timeline).every((item) => item.skipManualAffiliationSegments === false)).toBe(
595+
true,
596+
)
597+
})
598+
478599
test('dated MSA keeps its date range on the segment timeline', async ({ qx }) => {
479600
const [org] = await createOrganizations(
480601
qx,
@@ -584,6 +705,110 @@ describe('prepareMemberOrganizationAffiliationTimeline', () => {
584705
expect(duringOverlap).toBeDefined()
585706
})
586707

708+
test('longer stint wins when overlapping dated MOs share source and memberCount', async ({
709+
qx,
710+
}) => {
711+
const [longer, shorter] = await createOrganizations(
712+
qx,
713+
withOrganizationDefaults([{ displayName: 'Long Co' }, { displayName: 'Short Co' }]),
714+
)
715+
const [member] = await createMembers(qx, withMemberDefaults([{}]))
716+
717+
await createMemberOrganizations(qx, [
718+
{
719+
memberId: member.id,
720+
organizationId: longer.id,
721+
dateStart: '2020-01-01T00:00:00.000Z',
722+
dateEnd: '2023-01-01T00:00:00.000Z',
723+
source: OrganizationSource.ENRICHMENT_CRUSTDATA,
724+
},
725+
{
726+
memberId: member.id,
727+
organizationId: shorter.id,
728+
dateStart: '2021-01-01T00:00:00.000Z',
729+
dateEnd: '2022-01-01T00:00:00.000Z',
730+
source: OrganizationSource.ENRICHMENT_CRUSTDATA,
731+
},
732+
])
733+
734+
const timeline = await prepareMemberOrganizationAffiliationTimeline(qx, member.id)
735+
const duringOverlap = baseItems(timeline).find(
736+
(item) => item.organizationId === longer.id && item.dateStart === '2020-01-01T00:00:00.000Z',
737+
)
738+
739+
expect(duringOverlap).toBeDefined()
740+
expect(
741+
baseItems(timeline).some(
742+
(item) =>
743+
item.organizationId === shorter.id && item.dateStart === '2021-01-01T00:00:00.000Z',
744+
),
745+
).toBe(false)
746+
})
747+
748+
test('higher memberCount wins when overlapping dated MOs share source', async ({ qx }) => {
749+
const [popular, niche] = await createOrganizations(
750+
qx,
751+
withOrganizationDefaults([{ displayName: 'Popular Co' }, { displayName: 'Niche Co' }]),
752+
)
753+
const [member] = await createMembers(qx, withMemberDefaults([{}]))
754+
const { kubernetes } = await createLeafSegments(qx)
755+
756+
// Niche stint is longer; popular must win via memberCount, not longest-range.
757+
await createMemberOrganizations(qx, [
758+
{
759+
memberId: member.id,
760+
organizationId: popular.id,
761+
dateStart: '2021-01-01T00:00:00.000Z',
762+
dateEnd: '2022-01-01T00:00:00.000Z',
763+
source: OrganizationSource.ENRICHMENT_CRUSTDATA,
764+
},
765+
{
766+
memberId: member.id,
767+
organizationId: niche.id,
768+
dateStart: '2020-01-01T00:00:00.000Z',
769+
dateEnd: '2023-01-01T00:00:00.000Z',
770+
source: OrganizationSource.ENRICHMENT_CRUSTDATA,
771+
},
772+
])
773+
774+
await insertOrganizationSegments(qx, [
775+
{
776+
organizationId: popular.id,
777+
segmentId: kubernetes.id,
778+
memberCount: 10_000,
779+
activityCount: 1,
780+
activeOn: ['github'],
781+
joinedAt: '2020-01-01T00:00:00.000Z',
782+
lastActive: '2024-01-01T00:00:00.000Z',
783+
avgContributorEngagement: 0,
784+
},
785+
{
786+
organizationId: niche.id,
787+
segmentId: kubernetes.id,
788+
memberCount: 10,
789+
activityCount: 1,
790+
activeOn: ['github'],
791+
joinedAt: '2020-01-01T00:00:00.000Z',
792+
lastActive: '2024-01-01T00:00:00.000Z',
793+
avgContributorEngagement: 0,
794+
},
795+
])
796+
797+
const timeline = await prepareMemberOrganizationAffiliationTimeline(qx, member.id)
798+
799+
expect(
800+
baseItems(timeline).some(
801+
(item) =>
802+
item.organizationId === popular.id && item.dateStart === '2021-01-01T00:00:00.000Z',
803+
),
804+
).toBe(true)
805+
expect(
806+
baseItems(timeline).some(
807+
(item) => item.organizationId === niche.id && item.dateStart === '2021-01-01T00:00:00.000Z',
808+
),
809+
).toBe(false)
810+
})
811+
587812
test('company is preferred over university when dated stints overlap', async ({ qx }) => {
588813
const [company, university] = await createOrganizations(
589814
qx,
@@ -823,6 +1048,57 @@ describe('refreshMemberOrganizationAffiliations', () => {
8231048
expect(await countByOrg(qx, member.id, kubernetes.id, employer.id)).toBe(0)
8241049
})
8251050

1051+
test('dated MSA applies only inside its window; MO covers activities outside', async ({ qx }) => {
1052+
const [employer, sponsor] = await createOrganizations(
1053+
qx,
1054+
withOrganizationDefaults([{ displayName: 'Akuity Inc' }, { displayName: 'Red Hat' }]),
1055+
)
1056+
const { member, identities } = await createMemberWithIdentity(qx, { value: 'contributor' })
1057+
const { kubernetes } = await createLeafSegments(qx)
1058+
1059+
await createMemberOrganizations(qx, [
1060+
{
1061+
memberId: member.id,
1062+
organizationId: employer.id,
1063+
dateStart: '2020-01-01T00:00:00.000Z',
1064+
dateEnd: null,
1065+
source: OrganizationSource.UI,
1066+
},
1067+
])
1068+
1069+
await createMemberSegmentAffiliations(qx, [
1070+
{
1071+
memberId: member.id,
1072+
segmentId: kubernetes.id,
1073+
organizationId: sponsor.id,
1074+
dateStart: '2021-01-01T00:00:00.000Z',
1075+
dateEnd: '2021-06-01T00:00:00.000Z',
1076+
},
1077+
])
1078+
1079+
await createActivityRelations(qx, [
1080+
...generateActivityRelations({
1081+
memberId: member.id,
1082+
segmentId: kubernetes.id,
1083+
identities,
1084+
count: 40,
1085+
timestamp: { from: '2021-02-01T00:00:00.000Z', to: '2021-05-01T00:00:00.000Z' },
1086+
}),
1087+
...generateActivityRelations({
1088+
memberId: member.id,
1089+
segmentId: kubernetes.id,
1090+
identities,
1091+
count: 30,
1092+
timestamp: { from: '2022-01-01T00:00:00.000Z', to: '2022-06-01T00:00:00.000Z' },
1093+
}),
1094+
])
1095+
1096+
await refreshMemberOrganizationAffiliations(qx, member.id)
1097+
1098+
expect(await countByOrg(qx, member.id, kubernetes.id, sponsor.id)).toBe(40)
1099+
expect(await countByOrg(qx, member.id, kubernetes.id, employer.id)).toBe(30)
1100+
})
1101+
8261102
test('email-domain activities route to matching org over the date timeline', async ({ qx }) => {
8271103
const [emailOrg, datedOrg] = await createOrganizations(
8281104
qx,

0 commit comments

Comments
 (0)