diff --git a/.circleci/config.yml b/.circleci/config.yml index 287450a656..66e5333351 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,6 +23,7 @@ executors: # DB URL for the jest tests per ormconfig.test.ts TEST_DATABASE_URL: "postgres://bloom-ci@localhost:5432/bloom" PARTNERS_PORTAL_URL: "http://localhost:3001" + BACKEND_API_BASE: "http://localhost:3100" JURISDICTION_NAME: Bloomington GOOGLE_API_EMAIL: "secret-key" GOOGLE_API_ID: "secret-key" @@ -31,6 +32,13 @@ executors: SHOW_LOTTERY: "TRUE" SHOW_PUBLIC_LOTTERY: "TRUE" THROTTLE_LIMIT: 1000 + API_PASS_KEY: "some-key-here" + HOUSING_COUNSELOR_SERVICE_URL: "/get-assistance" + # Sample token https://docs.mapbox.com/api/accounts/tokens/#example-token-metadata-object + MAPBOX_TOKEN: "pk.eyJ1Ijoic2NvdGhpcyIsImEiOiJjaWp1Y2ltYmUwMDBicmJrdDQ4ZDBkaGN4In0.sbihZCZJ56-fsFNKHXF8YQ" + CLOUDINARY_KEY: "key" + CLOUDINARY_SIGNED_PRESET: "signedpreset" + CLOUDINARY_CLOUD_NAME: "exygy" standard-node: docker: diff --git a/api/test/integration/api-key-guard.e2e-spec.ts b/api/test/integration/api-key-guard.e2e-spec.ts index d7b85dfdf8..31137bd7bc 100644 --- a/api/test/integration/api-key-guard.e2e-spec.ts +++ b/api/test/integration/api-key-guard.e2e-spec.ts @@ -13,6 +13,7 @@ describe('API Key Guard Tests', () => { let cookies = ''; beforeAll(async () => { + process.env.API_PASS_KEY = 'OUR_API_PASS_KEY'; const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); diff --git a/api/test/integration/auth.e2e-spec.ts b/api/test/integration/auth.e2e-spec.ts index 14d8bf5a08..07269c3825 100644 --- a/api/test/integration/auth.e2e-spec.ts +++ b/api/test/integration/auth.e2e-spec.ts @@ -25,6 +25,8 @@ describe('Auth Controller Tests', () => { let smsService: SmsService; beforeAll(async () => { + process.env.TWILIO_ACCOUNT_SID = 'AC.SID'; + process.env.TWILIO_AUTH_TOKEN = 'TOKEN'; const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); diff --git a/api/test/integration/lottery.e2e-spec.ts b/api/test/integration/lottery.e2e-spec.ts index acd005132d..15a0a23adc 100644 --- a/api/test/integration/lottery.e2e-spec.ts +++ b/api/test/integration/lottery.e2e-spec.ts @@ -700,11 +700,9 @@ describe('Lottery Controller Tests', () => { describe('expireLotteries endpoint', () => { it('should only expire listing lotteries that are past due', async () => { + process.env.LOTTERY_DAYS_TILL_EXPIRY = '45'; const expiredClosedListingDate = dayjs(new Date()) - .subtract( - Number(process.env.LOTTERY_DAYS_TILL_EXPIRY || 45) + 1, - 'days', - ) + .subtract(46, 'days') .toDate(); const expiredListingData = await listingFactory(jurisdictionAId, prisma, { diff --git a/api/test/integration/throttle-guard.e2e-spec.ts b/api/test/integration/throttle-guard.e2e-spec.ts index 23804bf30a..33dfb42161 100644 --- a/api/test/integration/throttle-guard.e2e-spec.ts +++ b/api/test/integration/throttle-guard.e2e-spec.ts @@ -3,6 +3,7 @@ import { INestApplication } from '@nestjs/common'; import request from 'supertest'; import { AppModule } from '../../src/modules/app.module'; import { PrismaService } from '../../src/services/prisma.service'; +import { ThrottlerModule } from '@nestjs/throttler'; describe('Throttle Guard Tests', () => { let app: INestApplication; @@ -10,7 +11,15 @@ describe('Throttle Guard Tests', () => { beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], + imports: [ + AppModule, + ThrottlerModule.forRoot([ + { + ttl: Number('600000'), + limit: Number('10'), + }, + ]), + ], }).compile(); app = moduleFixture.createNestApplication(); @@ -26,7 +35,7 @@ describe('Throttle Guard Tests', () => { it('should succeed until throttle limit is exceeded', async () => { let res; // loop as many times as the THROTTLE_LIMIT allows - for (let i = 0; i < Number(process.env.THROTTLE_LIMIT); i++) { + for (let i = 0; i < 10; i++) { res = await request(app.getHttpServer()).get('/').expect(200); expect(res.body).toEqual({ success: true, diff --git a/api/test/integration/user.e2e-spec.ts b/api/test/integration/user.e2e-spec.ts index e42ea58454..eadf4b5ff7 100644 --- a/api/test/integration/user.e2e-spec.ts +++ b/api/test/integration/user.e2e-spec.ts @@ -44,6 +44,7 @@ describe('User Controller Tests', () => { }); beforeAll(async () => { + process.env.PARTNERS_PORTAL_URL = 'http://localhost:3001/'; const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }) diff --git a/api/test/unit/passports/jwt.strategy.spec.ts b/api/test/unit/passports/jwt.strategy.spec.ts index e3bf25a446..e4fc6bcf76 100644 --- a/api/test/unit/passports/jwt.strategy.spec.ts +++ b/api/test/unit/passports/jwt.strategy.spec.ts @@ -10,6 +10,7 @@ describe('Testing jwt strategy', () => { let strategy: JwtStrategy; let prisma: PrismaService; beforeAll(async () => { + process.env.APP_SECRET = 'SOME-LONG-SECRET-KEY'; const module: TestingModule = await Test.createTestingModule({ providers: [JwtStrategy, PrismaService], }).compile(); diff --git a/api/test/unit/passports/mfa.strategy.spec.ts b/api/test/unit/passports/mfa.strategy.spec.ts index 49ce90c6df..80e770ef65 100644 --- a/api/test/unit/passports/mfa.strategy.spec.ts +++ b/api/test/unit/passports/mfa.strategy.spec.ts @@ -50,6 +50,8 @@ describe('Testing mfa strategy', () => { }); it('should fail because user is locked out', async () => { + process.env.AUTH_LOCK_LOGIN_AFTER_FAILED_ATTEMPTS = '5'; + process.env.AUTH_LOCK_LOGIN_COOLDOWN = '1800000'; prisma.userAccounts.findFirst = jest.fn().mockResolvedValue({ id: randomUUID(), lastLoginAt: new Date(), @@ -475,6 +477,7 @@ describe('Testing mfa strategy', () => { }); it('should fail if no mfaCode is expired', async () => { + process.env.MFA_CODE_VALID = '60000'; const id = randomUUID(); prisma.userAccounts.findFirst = jest.fn().mockResolvedValue({ id: id, diff --git a/api/test/unit/passports/single-use-code.strategy.spec.ts b/api/test/unit/passports/single-use-code.strategy.spec.ts index ec5b890212..f38803b130 100644 --- a/api/test/unit/passports/single-use-code.strategy.spec.ts +++ b/api/test/unit/passports/single-use-code.strategy.spec.ts @@ -11,6 +11,9 @@ describe('Testing single-use-code strategy', () => { let strategy: SingleUseCodeStrategy; let prisma: PrismaService; beforeAll(async () => { + process.env.MFA_CODE_VALID = '60000'; + process.env.AUTH_LOCK_LOGIN_AFTER_FAILED_ATTEMPTS = '5'; + process.env.AUTH_LOCK_LOGIN_COOLDOWN = '1800000'; const module: TestingModule = await Test.createTestingModule({ providers: [SingleUseCodeStrategy, PrismaService], }).compile(); diff --git a/api/test/unit/services/auth.service.spec.ts b/api/test/unit/services/auth.service.spec.ts index c07facd38a..c76a3232c8 100644 --- a/api/test/unit/services/auth.service.spec.ts +++ b/api/test/unit/services/auth.service.spec.ts @@ -36,6 +36,14 @@ const mockedRecaptcha = RecaptchaEnterpriseServiceClient as unknown as jest.Mock; describe('Testing auth service', () => { + process.env.APP_SECRET = 'SOME-LONG-SECRET-KEY'; + process.env.EMAIL_API_KEY = 'SG.SOME-LONG-SECRET-KEY'; + process.env.TWILIO_ACCOUNT_SID = 'AC'; + process.env.TWILIO_AUTH_TOKEN = '842'; + process.env.MFA_CODE_LENGTH = '5'; + process.env.MFA_CODE_VALID = '60000'; + process.env.TWILIO_PHONE_NUMBER = '5555555555'; + process.env.GOOGLE_API_KEY = 'GOOGLE_API_KEY'; let authService: AuthService; let smsService: SmsService; let prisma: PrismaService; @@ -101,7 +109,7 @@ describe('Testing auth service', () => { sub: id, expiresIn: 86400000 / 24, }, - process.env.APP_SECRET, + 'SOME-LONG-SECRET-KEY', ), ); }); @@ -133,7 +141,7 @@ describe('Testing auth service', () => { sub: id, expiresIn: 86400000, }, - process.env.APP_SECRET, + 'SOME-LONG-SECRET-KEY', ), ); }); @@ -898,8 +906,8 @@ describe('Testing auth service', () => { }); expect(sendMfaCodeMock).not.toHaveBeenCalled(); expect(smsService.client.messages.create).toHaveBeenCalledWith({ - body: expect.anything(), - from: expect.anything(), + body: 'Your Partners Portal account access token: 00000', + from: '5555555555', to: '520-781-8711', }); expect(res).toEqual({ @@ -942,7 +950,7 @@ describe('Testing auth service', () => { { id, }, - process.env.APP_SECRET, + 'SOME-LONG-SECRET-KEY', ); const response = { cookie: jest.fn(), @@ -1003,14 +1011,14 @@ describe('Testing auth service', () => { { id, }, - process.env.APP_SECRET, + 'SOME-LONG-SECRET-KEY', ); const secondId = randomUUID(); const secondToken = sign( { id: secondId, }, - process.env.APP_SECRET, + 'SOME-LONG-SECRET-KEY', ); const response = { @@ -1044,7 +1052,7 @@ describe('Testing auth service', () => { { id, }, - process.env.APP_SECRET, + 'SOME-LONG-SECRET-KEY', ); prisma.userAccounts.findUnique = jest .fn() @@ -1105,7 +1113,7 @@ describe('Testing auth service', () => { id, email: 'example@exygy.com', }, - process.env.APP_SECRET, + 'SOME-LONG-SECRET-KEY', ); prisma.userAccounts.findUnique = jest .fn() diff --git a/api/test/unit/services/lottery.service.spec.ts b/api/test/unit/services/lottery.service.spec.ts index 2283a9a2b5..a62b979c0d 100644 --- a/api/test/unit/services/lottery.service.spec.ts +++ b/api/test/unit/services/lottery.service.spec.ts @@ -984,6 +984,7 @@ describe('Testing lottery service', () => { describe('Test expireLotteries endpoint', () => { it('should call the updateMany', async () => { + process.env.LOTTERY_DAYS_TILL_EXPIRY = '45'; prisma.listings.findMany = jest.fn().mockResolvedValue([ { id: 'example id1', diff --git a/api/test/unit/services/user.service.spec.ts b/api/test/unit/services/user.service.spec.ts index beaeab09eb..0d59932028 100644 --- a/api/test/unit/services/user.service.spec.ts +++ b/api/test/unit/services/user.service.spec.ts @@ -304,10 +304,11 @@ describe('Testing user service', () => { describe('createConfirmationToken', () => { it('should encode a confirmation token correctly', () => { + process.env.APP_SECRET = 'SOME-LONG-SECRET-KEY'; const id = randomUUID(); const res = service.createConfirmationToken(id, 'example@email.com'); expect(res).not.toBeNull(); - const decoded = verify(res, process.env.APP_SECRET) as IdDTO; + const decoded = verify(res, 'SOME-LONG-SECRET-KEY') as IdDTO; expect(decoded.id).toEqual(id); }); }); @@ -2059,6 +2060,8 @@ describe('Testing user service', () => { }); it('should successfully request single use code when previous code is still valid', async () => { + process.env.MFA_CODE_LENGTH = '5'; + process.env.MFA_CODE_VALID = '60000'; const id = randomUUID(); emailService.sendSingleUseCode = jest.fn(); prisma.userAccounts.findFirst = jest.fn().mockResolvedValue({ diff --git a/sites/partners/cypress/e2e/default/03-listing.spec.ts b/sites/partners/cypress/e2e/default/03-listing.spec.ts index a1e0f46c31..ffc21a4d72 100644 --- a/sites/partners/cypress/e2e/default/03-listing.spec.ts +++ b/sites/partners/cypress/e2e/default/03-listing.spec.ts @@ -85,6 +85,17 @@ describe("Listing Management Tests", () => { // Fill out a First Come, First Serve (FCFS) listing // eslint-disable-next-line @typescript-eslint/no-explicit-any function fillOutListing(cy: Cypress.cy, listing: any): void { + cy.intercept("GET", "/geocoding/v5/**", { fixture: "address" }) + cy.intercept("POST", "https://api.cloudinary.com/v1_1/exygy/upload", { + fixture: "cypressUpload", + }) + cy.intercept( + "GET", + "https://res.cloudinary.com/exygy/image/upload/w_400,c_limit,q_65/dev/cypress-automated-image-upload-071e2ab9-5a52-4f34-85f0-e41f696f4b96.jpg", + { + fixture: "cypress-automated-image-upload-071e2ab9-5a52-4f34-85f0-e41f696f4b96.jpeg", + } + ) cy.getByID("jurisdictions.id").select(listing["jurisdiction.id"]) cy.getByID("name").type(listing["name"]) cy.getByID("developer").type(listing["developer"]) @@ -106,6 +117,16 @@ describe("Listing Management Tests", () => { .should("have.attr", "src") .should("include", "cypress-automated-image-upload-071e2ab9-5a52-4f34-85f0-e41f696f4b96") + cy.intercept("POST", "https://api.cloudinary.com/v1_1/exygy/upload", { + public_id: "dev/cypress-automated-image-upload-46806882-b98d-49d7-ac83-8016ab4b2f08", + }) + cy.intercept( + "GET", + "https://res.cloudinary.com/exygy/image/upload/w_400,c_limit,q_65/dev/cypress-automated-image-upload-46806882-b98d-49d7-ac83-8016ab4b2f08.jpg", + { + fixture: "cypress-automated-image-upload-46806882-b98d-49d7-ac83-8016ab4b2f08.jpg", + } + ) cy.getByID("add-photos-button").contains("Edit Photos").click() cy.getByTestId("dropzone-input").attachFile( "cypress-automated-image-upload-46806882-b98d-49d7-ac83-8016ab4b2f08.jpg", diff --git a/sites/partners/cypress/e2e/default/09-lottery.spec.ts b/sites/partners/cypress/e2e/default/09-lottery.spec.ts index 5c8befb5b6..59af6349d3 100644 --- a/sites/partners/cypress/e2e/default/09-lottery.spec.ts +++ b/sites/partners/cypress/e2e/default/09-lottery.spec.ts @@ -9,7 +9,16 @@ describe("Lottery Tests", () => { it("can run through every lottery action", () => { const uniqueListingName = Date.now().toString() - + cy.intercept("POST", "https://api.cloudinary.com/v1_1/exygy/upload", { + fixture: "cypressUpload", + }) + cy.intercept( + "GET", + "https://res.cloudinary.com/exygy/image/upload/w_400,c_limit,q_65/dev/cypress-automated-image-upload-071e2ab9-5a52-4f34-85f0-e41f696f4b96.jpeg.jpg", + { + fixture: "cypress-automated-image-upload-071e2ab9-5a52-4f34-85f0-e41f696f4b96.jpeg", + } + ) cy.visit("/") cy.addMinimalListing(uniqueListingName, true, false, true) cy.addMinimalApplication(uniqueListingName) diff --git a/sites/partners/cypress/e2e/listings-approval/listings-approval.spec.ts b/sites/partners/cypress/e2e/listings-approval/listings-approval.spec.ts index e981a9fa6f..336d2c9785 100644 --- a/sites/partners/cypress/e2e/listings-approval/listings-approval.spec.ts +++ b/sites/partners/cypress/e2e/listings-approval/listings-approval.spec.ts @@ -2,6 +2,16 @@ describe("Listings approval feature", () => { const uniqueListingName = Date.now().toString() const uniqueListingNameEdited = `${uniqueListingName} edited` it("should allow for pending submission, requested changes, and approval", () => { + cy.intercept("POST", "https://api.cloudinary.com/v1_1/exygy/upload", { + fixture: "cypressUpload", + }) + cy.intercept( + "GET", + "https://res.cloudinary.com/exygy/image/upload/w_400,c_limit,q_65/dev/cypress-automated-image-upload-071e2ab9-5a52-4f34-85f0-e41f696f4b96.jpeg.jpg", + { + fixture: "cypress-automated-image-upload-071e2ab9-5a52-4f34-85f0-e41f696f4b96.jpeg", + } + ) // Partner: Submit a listing for approval cy.login("jurisdictionalAdminUser") cy.visit("/") diff --git a/sites/partners/cypress/fixtures/address.json b/sites/partners/cypress/fixtures/address.json new file mode 100644 index 0000000000..7edaa054d4 --- /dev/null +++ b/sites/partners/cypress/fixtures/address.json @@ -0,0 +1,56 @@ +{ + "type": "FeatureCollection", + "query": ["600", "mongomery", "st", "san", "francisco", "ca", "94112"], + "features": [ + { + "id": "address.4226420775908864", + "type": "Feature", + "place_type": ["address"], + "relevance": 0.797326, + "properties": { + "accuracy": "rooftop", + "mapbox_id": "dXJuOm1ieGFkcjo3ODFjNjQxYi1mMzI2LTQxMTYtOTg3My1lOWNiNDJmNjg1ZWQ=" + }, + "text": "Montgomery Street", + "place_name": "600 Montgomery Street, San Francisco, California 94111, United States", + "center": [-122.40273, 37.79516], + "geometry": { "type": "Point", "coordinates": [-122.40273, 37.79516] }, + "address": "600", + "context": [ + { + "id": "neighborhood.214314220", + "mapbox_id": "dXJuOm1ieHBsYzpETVlzN0E", + "text": "Financial District" + }, + { "id": "postcode.312487660", "mapbox_id": "dXJuOm1ieHBsYzpFcUF1N0E", "text": "94111" }, + { + "id": "place.292358380", + "wikidata": "Q62", + "mapbox_id": "dXJuOm1ieHBsYzpFVzBJN0E", + "text": "San Francisco" + }, + { + "id": "district.20547308", + "wikidata": "Q62", + "mapbox_id": "dXJuOm1ieHBsYzpBVG1HN0E", + "text": "San Francisco County" + }, + { + "id": "region.419052", + "short_code": "US-CA", + "wikidata": "Q99", + "mapbox_id": "dXJuOm1ieHBsYzpCbVRz", + "text": "California" + }, + { + "id": "country.8940", + "short_code": "us", + "wikidata": "Q30", + "mapbox_id": "dXJuOm1ieHBsYzpJdXc", + "text": "United States" + } + ] + } + ], + "attribution": "NOTICE: © 2023 Mapbox and its suppliers. All rights reserved. Use of this data is subject to the Mapbox Terms of Service (https://www.mapbox.com/about/maps/). This response and the information it contains may not be retained. POI(s) provided by Foursquare." +} diff --git a/sites/partners/cypress/fixtures/cypressUpload.json b/sites/partners/cypress/fixtures/cypressUpload.json new file mode 100644 index 0000000000..b69737b3d1 --- /dev/null +++ b/sites/partners/cypress/fixtures/cypressUpload.json @@ -0,0 +1,4 @@ +{ + "asset_id": "c225fe21edd2f291ddf93c9d57913567", + "public_id": "dev/cypress-automated-image-upload-071e2ab9-5a52-4f34-85f0-e41f696f4b96" +} diff --git a/sites/public/cypress/e2e/pages/submit-application.spec.ts b/sites/public/cypress/e2e/pages/submit-application.spec.ts index f24792bde1..177b0696d8 100644 --- a/sites/public/cypress/e2e/pages/submit-application.spec.ts +++ b/sites/public/cypress/e2e/pages/submit-application.spec.ts @@ -2,9 +2,25 @@ import { ElmVillageApplication, minimalDataApplication } from "../../mockData/ap describe("Submit", function () { it("should submit an application for the Elm Village listing", function () { + cy.intercept("GET", "/geocoding/v5/**", { fixture: "address" }) + // Interceptor for the address in the preference + cy.intercept( + "GET", + "https://api.mapbox.com/geocoding/v5/mapbox.places/1600%20pennsylvania%20ave%2C%20Washington%2C%20DC%2020500%2C%20United%20States.json**", + { + features: [ + { + place_name: + "1600 pennsylvania ave, Washington, District of Columbia 20500, United States", + }, + ], + } + ) + cy.submitApplication("Elm Village", ElmVillageApplication, false, true) }) it("should submit a minimal application for the Test: Default, No Preferences", function () { + cy.intercept("GET", "/geocoding/v5/**", { fixture: "address" }) cy.submitApplication("Blue Sky Apartments", minimalDataApplication, false, false) }) })