Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 32 additions & 34 deletions api/prisma/seed-staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ export const defaultRaceEthnicityConfiguration: RaceEthnicityConfiguration = {
],
};

const applicationFactoryMany = async (
count: number,
optionalParams?: Parameters<typeof applicationFactory>[0],
): Promise<Prisma.ApplicationsCreateInput[]> => {
return Promise.all(
Array.from({ length: count }, async () =>
applicationFactory(optionalParams),
),
);
};

export const stagingSeed = async (
prismaClient: PrismaClient,
jurisdictionName: string,
Expand Down Expand Up @@ -596,7 +607,7 @@ export const stagingSeed = async (
jurisdictionsId: angelopolisJurisdiction.id,
},
});
await prismaClient.userAccounts.create({
const advocate = await prismaClient.userAccounts.create({
data: await userFactory({
email: 'advocate@example.com',
confirmedAt: new Date(),
Expand All @@ -605,6 +616,7 @@ export const stagingSeed = async (
agencyId: agency.id,
}),
});

// add jurisdiction specific translations and default ones
await prismaClient.translations.create({
data: translationFactory({
Expand Down Expand Up @@ -929,7 +941,11 @@ export const stagingSeed = async (
// create pre-determined values
const unitTypes = await unitTypeFactoryAll(prismaClient);
await reservedCommunityTypeFactoryAll(mainJurisdiction.id, prismaClient);
const expiredApplicationDate = process.env.APPLICATION_DAYS_TILL_EXPIRY
? dayjs(new Date()).subtract(10, 'days').toDate()
: undefined;
// list of predefined listings WARNING: images only work if image setup is cloudinary on exygy account

const listingsToCreate: Parameters<typeof listingFactory>[] = [
[
angelopolisJurisdiction.id,
Expand Down Expand Up @@ -982,12 +998,13 @@ export const stagingSeed = async (
hearingVisionAccessibilityNeedsProgramQuestion,
],
applications: [
await applicationFactory({
...(await applicationFactoryMany(2, {
raceEthnicityConfiguration: angelopolisRaceEthnicityConfiguration,
}),
await applicationFactory({
})),
...(await applicationFactoryMany(20, {
raceEthnicityConfiguration: angelopolisRaceEthnicityConfiguration,
}),
userId: advocate.id,
})),
],
userAccounts: [{ id: partnerUser.id }],
optionalFeatures: { carpetInUnit: true },
Expand Down Expand Up @@ -1056,8 +1073,7 @@ export const stagingSeed = async (
multiselectQuestions: [cityEmployeeQuestion],
// has applications that are the same email and also same name/dob
applications: [
await applicationFactory(),
await applicationFactory(),
...(await applicationFactoryMany(2)),
await applicationFactory({
submissionType: ApplicationSubmissionTypeEnum.paper,
}),
Expand Down Expand Up @@ -1101,12 +1117,9 @@ export const stagingSeed = async (
birthYear: 1970,
},
}),
await applicationFactory({
applicant: { emailAddress: 'user2@example.com' },
}),
await applicationFactory({
...(await applicationFactoryMany(2, {
applicant: { emailAddress: 'user2@example.com' },
}),
})),
await applicationFactory({
applicant: {
emailAddress: 'user3@example.com',
Expand Down Expand Up @@ -1220,28 +1233,16 @@ export const stagingSeed = async (
applications: [
await applicationFactory({
isNewest: true,
expireAfter: process.env.APPLICATION_DAYS_TILL_EXPIRY
? dayjs(new Date()).subtract(10, 'days').toDate()
: undefined,
expireAfter: expiredApplicationDate,
}),
// applications below should have their PII removed via the cron job
await applicationFactory({
isNewest: false,
expireAfter: process.env.APPLICATION_DAYS_TILL_EXPIRY
? dayjs(new Date()).subtract(10, 'days').toDate()
: undefined,
}),
await applicationFactory({
...(await applicationFactoryMany(2, {
isNewest: false,
expireAfter: process.env.APPLICATION_DAYS_TILL_EXPIRY
? dayjs(new Date()).subtract(10, 'days').toDate()
: undefined,
}),
expireAfter: expiredApplicationDate,
})),
await applicationFactory({
isNewest: false,
expireAfter: process.env.APPLICATION_DAYS_TILL_EXPIRY
? dayjs(new Date()).subtract(10, 'days').toDate()
: undefined,
expireAfter: expiredApplicationDate,
householdMember: [
householdMemberFactorySingle(1, {}),
householdMemberFactorySingle(2, {}),
Expand Down Expand Up @@ -1280,12 +1281,9 @@ export const stagingSeed = async (
await applicationFactory({
multiselectQuestions: [workInCityQuestion, cityEmployeeQuestion],
}),
await applicationFactory({
...(await applicationFactoryMany(2, {
multiselectQuestions: [workInCityQuestion],
}),
await applicationFactory({
multiselectQuestions: [workInCityQuestion],
}),
})),
await applicationFactory(),
],
multiselectQuestions: [
Expand Down
6 changes: 6 additions & 0 deletions api/src/dtos/applications/public-apps-view-params.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,10 @@ export class PublicAppsViewQueryParams extends PaginationQueryParams {
{ toClassOnly: true },
)
includeLotteryApps?: boolean;

@Expose()
@ApiPropertyOptional()
@IsOptional({ groups: [ValidationsGroupsEnum.default] })
@IsString({ groups: [ValidationsGroupsEnum.default] })
applicantNameSearch?: string;
}
54 changes: 48 additions & 6 deletions api/src/services/application.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,12 +424,48 @@ export class ApplicationService {
if (!user) {
throw new ForbiddenException();
}
const whereClause = this.buildWhereClause(params);
const normalizedParams = {
...params,
userId: user.id,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: Reminder to self to check in

};
const whereClause = this.buildWhereClause(normalizedParams);

if (user.isAdvocate && normalizedParams.applicantNameSearch) {
const searchTerms = normalizedParams.applicantNameSearch
.trim()
.split(/\s+/)
.filter(Boolean);

if (Array.isArray(whereClause.AND) && searchTerms.length > 0) {
whereClause.AND.push({
AND: searchTerms.map((term) => ({
OR: [
{
applicant: {
firstName: {
contains: term,
mode: 'insensitive',
},
},
},
{
applicant: {
lastName: {
contains: term,
mode: 'insensitive',
},
},
},
],
})),
});
}
}

const buildCountWhereClause = (filterType: ApplicationsFilterEnum) =>
this.buildPublicAppsViewWhereClause(
{
...params,
...normalizedParams,
filterType,
},
whereClause,
Expand All @@ -452,12 +488,12 @@ export class ApplicationService {
const totalCount = openCount + closedCount + lotteryCount;

const displayWhereClause = this.buildPublicAppsViewWhereClause(
params,
normalizedParams,
whereClause,
);

let displayCount = totalCount;
switch (params.filterType) {
switch (normalizedParams.filterType) {
case ApplicationsFilterEnum.open:
displayCount = openCount;
break;
Expand All @@ -472,8 +508,8 @@ export class ApplicationService {
break;
}

const limit = params.limit ?? 10;
let page = params.page ?? 1;
const limit = normalizedParams.limit ?? 10;
let page = normalizedParams.page ?? 1;

if (displayCount && limit && page > 1) {
if (Math.ceil(displayCount / limit) < page) {
Expand All @@ -491,6 +527,12 @@ export class ApplicationService {
updatedAt: true,
status: true,
markedAsDuplicate: true,
applicant: {
select: {
firstName: true,
lastName: true,
},
},
listings: {
select: {
id: true,
Expand Down
54 changes: 37 additions & 17 deletions api/test/integration/application.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,15 @@ describe('Application Controller Tests', () => {

expect(res.body.items.length).toBeGreaterThanOrEqual(2);
const resApplicationA = res.body.items.find(
(item) => item.applicant.firstName === applicationA.applicant.firstName,
(item) =>
item.applicant?.firstName === applicationA.applicant.firstName,
);
expect(resApplicationA).not.toBeNull();
res.body.items.find(
(item) => item.applicant.firstName === applicationB.applicant.firstName,
const resApplicationB = res.body.items.find(
(item) =>
item.applicant?.firstName === applicationB.applicant.firstName,
);
expect(resApplicationA).not.toBeNull();
expect(resApplicationB).not.toBeNull();
});

it('should get stored applications when no params sent', async () => {
Expand Down Expand Up @@ -359,13 +361,15 @@ describe('Application Controller Tests', () => {

expect(res.body.items.length).toBeGreaterThanOrEqual(2);
const resApplicationA = res.body.items.find(
(item) => item.applicant.firstName === applicationA.applicant.firstName,
(item) =>
item.applicant?.firstName === applicationA.applicant.firstName,
);
expect(resApplicationA).not.toBeNull();
res.body.items.find(
(item) => item.applicant.firstName === applicationB.applicant.firstName,
const resApplicationB = res.body.items.find(
(item) =>
item.applicant?.firstName === applicationB.applicant.firstName,
);
expect(resApplicationA).not.toBeNull();
expect(resApplicationB).not.toBeNull();
});
});

Expand Down Expand Up @@ -2078,14 +2082,32 @@ describe('Application Controller Tests', () => {
});

describe('publicAppsView endpoint', () => {
const createAndLoginUser = async () => {
const user = await prisma.userAccounts.create({
data: await userFactory({
mfaEnabled: false,
confirmedAt: new Date(),
}),
});

const resLogIn = await request(app.getHttpServer())
.post('/auth/login')
.set({ passkey: process.env.API_PASS_KEY || '' })
.send({
email: user.email,
password: 'Abcdef12345!',
} as Login)
.expect(201);

return { user, cookies: resLogIn.headers['set-cookie'] };
};

it('should retrieve applications and counts when they exist', async () => {
const unitTypeA = await unitTypeFactorySingle(
prisma,
UnitTypeEnum.oneBdrm,
);
const user = await prisma.userAccounts.create({
data: await userFactory(),
});
const { user, cookies } = await createAndLoginUser();
const juris = await prisma.jurisdictions.create({
data: jurisdictionFactory(),
});
Expand Down Expand Up @@ -2152,7 +2174,7 @@ describe('Application Controller Tests', () => {
const res = await request(app.getHttpServer())
.get(`/applications/publicAppsView?${query}`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.set('Cookie', adminCookies)
.set('Cookie', cookies)
.expect(200);

expect(res.body.applicationsCount.total).toEqual(3);
Expand All @@ -2168,12 +2190,10 @@ describe('Application Controller Tests', () => {
});

it('should not retrieve applications nor error when none exist', async () => {
const userA = await prisma.userAccounts.create({
data: await userFactory(),
});
const { user, cookies } = await createAndLoginUser();

const queryParams: PublicAppsViewQueryParams = {
userId: userA.id,
userId: user.id,
filterType: ApplicationsFilterEnum.all,
includeLotteryApps: true,
page: 1,
Expand All @@ -2184,7 +2204,7 @@ describe('Application Controller Tests', () => {
const res = await request(app.getHttpServer())
.get(`/applications/publicAppsView?${query}`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.set('Cookie', adminCookies)
.set('Cookie', cookies)
.expect(200);

expect(res.body.applicationsCount.total).toEqual(0);
Expand Down
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: These perm tests were sometimes flaky because of not mocking cron job creates, so added this mock to all these tests

Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { EmailAndAppUrl } from '../../../src/dtos/users/email-and-app-url.dto';
import { ConfirmationRequest } from '../../../src/dtos/users/confirmation-request.dto';
import { UserService } from '../../../src/services/user.service';
import { EmailService } from '../../../src/services/email.service';
import { CronJobService } from '../../../src/services/cron-job.service';
import { permissionActions } from '../../../src/enums/permissions/permission-actions-enum';
import { AfsResolve } from '../../../src/dtos/application-flagged-sets/afs-resolve.dto';
import {
Expand Down Expand Up @@ -75,6 +76,11 @@ const testEmailService = {
applicationConfirmation: jest.fn(),
};

const testCronJobService = {
startCronJob: jest.fn().mockResolvedValue(undefined),
markCronJobAsStarted: jest.fn().mockResolvedValue(undefined),
};

describe('Testing Permissioning of endpoints as Admin User', () => {
let app: INestApplication;
let prisma: PrismaService;
Expand All @@ -88,6 +94,8 @@ describe('Testing Permissioning of endpoints as Admin User', () => {
})
.overrideProvider(EmailService)
.useValue(testEmailService)
.overrideProvider(CronJobService)
.useValue(testCronJobService)
.compile();

app = moduleFixture.createNestApplication();
Expand Down
Loading
Loading