Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2370 - Institution Read-Only User Type - PR 2 #4214

Merged
merged 30 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b4d1681
initial commit
andrepestana-aot Jan 8, 2025
c6a16d5
merge from main
andrepestana-aot Jan 8, 2025
befa7a7
checking GHA tests failing
andrepestana-aot Jan 8, 2025
f0739ed
checking GHA tests failing
andrepestana-aot Jan 8, 2025
bb0a008
checking GHA tests failing
andrepestana-aot Jan 8, 2025
4afbf19
checking GHA tests failing
andrepestana-aot Jan 8, 2025
6590133
checking GHA tests failing
andrepestana-aot Jan 8, 2025
5f2b21e
checking GHA tests failing
andrepestana-aot Jan 8, 2025
faa8138
checking GHA tests failing
andrepestana-aot Jan 8, 2025
f8bbc93
checking GHA tests failing
andrepestana-aot Jan 8, 2025
113fc5a
checking GHA tests failing
andrepestana-aot Jan 8, 2025
3428dc9
checking GHA tests failing
andrepestana-aot Jan 8, 2025
f88441c
checking GHA tests failing
andrepestana-aot Jan 8, 2025
b0e6c65
checking GHA tests failing
andrepestana-aot Jan 8, 2025
d758af1
checking GHA tests failing
andrepestana-aot Jan 8, 2025
01a8e7b
checking GHA tests failing
andrepestana-aot Jan 9, 2025
3f904b2
checking GHA tests failing
andrepestana-aot Jan 9, 2025
807281b
checking GHA tests failing
andrepestana-aot Jan 9, 2025
671cf4f
checking GHA tests failing
andrepestana-aot Jan 9, 2025
2eacff8
checking GHA tests failing
andrepestana-aot Jan 9, 2025
1e62d6c
reverting changes for testing
andrepestana-aot Jan 9, 2025
da93bbb
var name and comment
andrepestana-aot Jan 9, 2025
16acc54
comment
andrepestana-aot Jan 9, 2025
cceddf5
review issues
andrepestana-aot Jan 9, 2025
ec91564
review issues
andrepestana-aot Jan 9, 2025
f1dbdb9
review issues
andrepestana-aot Jan 9, 2025
12a540e
review issues
andrepestana-aot Jan 10, 2025
7d1bf1c
review issues
andrepestana-aot Jan 10, 2025
54d6dab
review issues
andrepestana-aot Jan 13, 2025
1812ed2
fixed import
andrepestana-aot Jan 13, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@ import {
import { ConfigModule, ConfigService } from "@sims/utilities/config";
import { createZeebeModuleMock } from "@sims/test-utils/mocks";
import { AppModule } from "../../../app.module";
import { AuthTestController } from "../../../testHelpers/controllers/auth-test/auth-test.controller";
import { DiscoveryModule } from "@golevelup/nestjs-discovery";
import { DataSource } from "typeorm";
import { JwtService } from "@nestjs/jwt";
import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants";
import {
BEARER_AUTH_TYPE,
getAuthorizedLocation,
InstitutionTokenTypes,
mockUserLoginInfo,
resetMockUserLoginInfo,
} from "../../../testHelpers";
import * as dayjs from "dayjs";
import { Student, User } from "@sims/sims-db";
import { AuthTestController } from "../../../testHelpers/controllers/auth-test/auth-test.controller";
import { SIMS2_COLLE_USER } from "@sims/test-utils/constants";
import { InstitutionUserTypes } from "../../user-types.enum";

describe("Authentication (e2e)", () => {
// Nest application to be shared for all e2e tests
Expand All @@ -44,6 +48,7 @@ describe("Authentication (e2e)", () => {
let db: E2EDataSources;
let studentDecodedToken: any;
let moduleFixture: TestingModule;
let collegEInstitutionReadOnlyUserAccessToken: string;

beforeAll(async () => {
await KeycloakConfig.load();
Expand All @@ -63,6 +68,13 @@ describe("Authentication (e2e)", () => {
);
aestAccessToken = aestToken.access_token;

const collegEToken = await KeycloakService.shared.getToken(
SIMS2_COLLE_USER,
process.env.E2E_TEST_PASSWORD,
"institution",
);
collegEInstitutionReadOnlyUserAccessToken = collegEToken.access_token;

moduleFixture = await Test.createTestingModule({
imports: [AppModule, createZeebeModuleMock(), DiscoveryModule],
// AuthTestController is used only for e2e tests and could be
Expand Down Expand Up @@ -296,6 +308,43 @@ describe("Authentication (e2e)", () => {
.expect(HttpStatus.OK);
},
);

it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => {
// Arrange
const collegeELocation = await getAuthorizedLocation(
db,
InstitutionTokenTypes.CollegeEReadOnlyUser,
InstitutionUserTypes.readOnlyUser,
);
const endpoint = `/auth-test/institution-location-reading-route/${collegeELocation.id}`;

// Act/Assert
await request(app.getHttpServer())
.get(endpoint)
.auth(collegEInstitutionReadOnlyUserAccessToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.OK);
});

it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => {
// Arrange
const collegeELocation = await getAuthorizedLocation(
db,
InstitutionTokenTypes.CollegeEReadOnlyUser,
InstitutionUserTypes.readOnlyUser,
);
const endpoint = `/auth-test/institution-location-modifying-route/${collegeELocation.id}`;

// Act/Assert
await request(app.getHttpServer())
.get(endpoint)
.auth(collegEInstitutionReadOnlyUserAccessToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.FORBIDDEN)
.expect({
statusCode: HttpStatus.FORBIDDEN,
message: "Forbidden resource",
error: "Forbidden",
});
});
});

afterAll(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export enum InstitutionUserTypes {
admin = "admin",
user = "user",
readOnlyUser = "read-only-user",
andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
}

export enum InstitutionUserRoles {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createTestingAppModule,
getAuthRelatedEntities,
getInstitutionToken,
getAuthorizedLocation,
InstitutionTokenTypes,
} from "../../../../testHelpers";
import {
Expand All @@ -26,6 +27,7 @@ import { createFakeSINValidation } from "@sims/test-utils/factories/sin-validati
import { addDays, getISODateOnlyString } from "@sims/utilities";
import { STUDY_DATE_OVERLAP_ERROR } from "../../../../utilities";
import { OFFERING_INTENSITY_MISMATCH } from "../../../../constants";
import { InstitutionUserTypes } from "../../../../auth";

describe("ApplicationOfferingChangeRequestInstitutionsController(e2e)-createApplicationOfferingChangeRequest", () => {
let app: INestApplication;
Expand Down Expand Up @@ -132,6 +134,30 @@ describe("ApplicationOfferingChangeRequestInstitutionsController(e2e)-createAppl
);
});

it("Should not be able to submit application offering request with a read-only user.", async () => {
// Arrange
const collegeELocation = await getAuthorizedLocation(
db,
InstitutionTokenTypes.CollegeEReadOnlyUser,
InstitutionUserTypes.readOnlyUser,
);
const endpoint = `/institutions/location/${collegeELocation.id}/application-offering-change-request`;
const collegEInstitutionUserToken = await getInstitutionToken(
InstitutionTokenTypes.CollegeEReadOnlyUser,
);

// Act/Assert
await request(app.getHttpServer())
.post(endpoint)
.auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.FORBIDDEN)
.expect({
statusCode: HttpStatus.FORBIDDEN,
message: "Forbidden resource",
error: "Forbidden",
});
});

it("Should throw study overlap error when trying to submit application offering request with an offering, which overlap with students another application.", async () => {
// Arrange
const savedUser = await db.user.save(createFakeUser());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
} from "../../services";
import { ApplicationOfferingChangeRequestStatus } from "@sims/sims-db";
import { PrimaryIdentifierAPIOutDTO } from "../models/primary.identifier.dto";
import { IInstitutionUserToken } from "../../auth";
import { IInstitutionUserToken, InstitutionUserTypes } from "../../auth";
import { CustomNamedError } from "@sims/utilities";
import {
EDUCATION_PROGRAM_IS_EXPIRED,
Expand Down Expand Up @@ -271,6 +271,7 @@ export class ApplicationOfferingChangeRequestInstitutionsController extends Base
@ApiUnauthorizedResponse({
description: "The location does not have access to the offering.",
})
@HasLocationAccess("locationId", [InstitutionUserTypes.user])
andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
@Post()
async createApplicationOfferingChangeRequest(
@UserToken() userToken: IInstitutionUserToken,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import {
createTestingAppModule,
getAuthRelatedEntities,
getInstitutionToken,
getAuthorizedLocation,
InstitutionTokenTypes,
} from "../../../../testHelpers";
import {
createE2EDataSources,
createFakeDisbursementValue,
createFakeInstitutionLocation,
E2EDataSources,
saveFakeApplicationDisbursements,
} from "@sims/test-utils";
import {
Expand All @@ -28,6 +31,7 @@ import {
} from "@sims/sims-db";
import { MONEY_VALUE_FOR_UNKNOWN_MAX_VALUE } from "../../../../utilities";
import { COE_WINDOW, addDays, getISODateOnlyString } from "@sims/utilities";
import { InstitutionUserTypes } from "../../../../auth";

describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment", () => {
let app: INestApplication;
Expand All @@ -37,6 +41,7 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment"
let offeringRepo: Repository<EducationProgramOffering>;
let collegeC: Institution;
let collegeCLocation: InstitutionLocation;
let db: E2EDataSources;
let sequenceControlRepo: Repository<SequenceControl>;

beforeAll(async () => {
Expand All @@ -46,6 +51,7 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment"
applicationRepo = dataSource.getRepository(Application);
disbursementScheduleRepo = dataSource.getRepository(DisbursementSchedule);
offeringRepo = dataSource.getRepository(EducationProgramOffering);
db = createE2EDataSources(dataSource);
sequenceControlRepo = dataSource.getRepository(SequenceControl);

const { institution } = await getAuthRelatedEntities(
Expand Down Expand Up @@ -141,6 +147,30 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment"
expect(sequenceControl).toBeDefined();
});

it("Should not allow the COE confirmation when the user is read-only.", async () => {
// Arrange
const collegeELocation = await getAuthorizedLocation(
db,
InstitutionTokenTypes.CollegeEReadOnlyUser,
InstitutionUserTypes.readOnlyUser,
);
const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/999999/confirm`;
const collegEInstitutionUserToken = await getInstitutionToken(
InstitutionTokenTypes.CollegeEReadOnlyUser,
);

// Act/Assert
await request(app.getHttpServer())
.patch(endpoint)
.auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.FORBIDDEN)
.expect({
statusCode: HttpStatus.FORBIDDEN,
message: "Forbidden resource",
error: "Forbidden",
});
});

it("Should allow the second COE confirmation when the application is on Completed status and all the conditions are fulfilled.", async () => {
// Arrange
const application = await saveFakeApplicationDisbursements(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createTestingAppModule,
getAuthRelatedEntities,
getInstitutionToken,
getAuthorizedLocation,
InstitutionTokenTypes,
} from "../../../../testHelpers";
import {
Expand All @@ -27,6 +28,7 @@ import {
StudentAssessmentStatus,
} from "@sims/sims-db";
import { COE_WINDOW, addDays, getISODateOnlyString } from "@sims/utilities";
import { InstitutionUserTypes } from "../../../../auth";

describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-denyConfirmationOfEnrollment", () => {
let app: INestApplication;
Expand Down Expand Up @@ -83,6 +85,30 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-denyConfirmationOf
expect(declinedCOE.coeDeniedReason.id).toBe(coeDenyReasonId);
});

it("Should not decline the COE when user is read-only.", async () => {
// Arrange
const collegeELocation = await getAuthorizedLocation(
db,
InstitutionTokenTypes.CollegeEReadOnlyUser,
InstitutionUserTypes.readOnlyUser,
);
const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/9999999/deny`;
const collegEInstitutionUserToken = await getInstitutionToken(
InstitutionTokenTypes.CollegeEReadOnlyUser,
);

// Act/Assert
await request(app.getHttpServer())
.patch(endpoint)
.auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.FORBIDDEN)
.expect({
statusCode: HttpStatus.FORBIDDEN,
message: "Forbidden resource",
error: "Forbidden",
});
});

it(
"Should decline the COE and create reassessment for impacted application(s) " +
"when institution decline a COE and the application that belongs to COE being declined can potentially " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
ENROLMENT_INVALID_OPERATION_IN_THE_CURRENT_STATE,
ENROLMENT_NOT_FOUND,
} from "@sims/services/constants";
import { InstitutionUserTypes } from "../../auth";

@AllowAuthorizedParty(AuthorizedParties.institution)
@Controller("location")
Expand Down Expand Up @@ -245,7 +246,7 @@ export class ConfirmationOfEnrollmentInstitutionsController extends BaseControll
* @param locationId location id of the application.
* @param payload COE confirmation information.
*/
@HasLocationAccess("locationId")
@HasLocationAccess("locationId", [InstitutionUserTypes.user])
@ApiNotFoundResponse({
description: "Enrolment not found.",
})
Expand Down Expand Up @@ -283,7 +284,7 @@ export class ConfirmationOfEnrollmentInstitutionsController extends BaseControll
* @param payload contains the denied reason of the
* student application.
*/
@HasLocationAccess("locationId")
@HasLocationAccess("locationId", [InstitutionUserTypes.user])
@ApiNotFoundResponse({
description: "Enrolment not found.",
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
getAuthRelatedEntities,
createFakeEducationProgram,
createFakeEducationProgramOffering,
getAuthorizedLocation,
} from "../../../../testHelpers";
import * as request from "supertest";
import * as faker from "faker";
Expand All @@ -36,6 +37,7 @@ import {
} from "../../models/education-program-offering.dto";
import { WILComponentOptions } from "../../../../services";
import { getISODateOnlyString } from "@sims/utilities";
import { InstitutionUserTypes } from "../../../../auth";

describe("EducationProgramOfferingInstitutionsController(e2e)-createOffering", () => {
let app: INestApplication;
Expand Down Expand Up @@ -198,6 +200,30 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-createOffering", (
);
});

it("Should not create a new offering when user is read-only.", async () => {
// Arrange
const collegeELocation = await getAuthorizedLocation(
db,
InstitutionTokenTypes.CollegeEReadOnlyUser,
InstitutionUserTypes.readOnlyUser,
);
const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999`;
const collegEInstitutionUserToken = await getInstitutionToken(
InstitutionTokenTypes.CollegeEReadOnlyUser,
);

// Act/Assert
await request(app.getHttpServer())
.post(endpoint)
.auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.FORBIDDEN)
.expect({
statusCode: HttpStatus.FORBIDDEN,
message: "Forbidden resource",
error: "Forbidden",
});
});

it("Should throw error when education program is expired.", async () => {
// Arrange
const fakeEducationProgram = createFakeEducationProgram(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
getAuthRelatedEntities,
createFakeEducationProgram,
createFakeEducationProgramOffering,
getAuthorizedLocation,
} from "../../../../testHelpers";
import * as request from "supertest";
import * as faker from "faker";
Expand All @@ -30,6 +31,7 @@ import {
MONEY_VALUE_FOR_UNKNOWN_MAX_VALUE,
} from "../../../../utilities";
import { getISODateOnlyString } from "@sims/utilities";
import { InstitutionUserTypes } from "../../../../auth";

describe("EducationProgramOfferingInstitutionsController(e2e)-updateProgramOffering", () => {
let app: INestApplication;
Expand Down Expand Up @@ -160,6 +162,30 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-updateProgramOffer
);
});

it("Should not update a new offering when requested by a read-only user.", async () => {
// Arrange
const collegeELocation = await getAuthorizedLocation(
db,
InstitutionTokenTypes.CollegeEReadOnlyUser,
InstitutionUserTypes.readOnlyUser,
);
const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999/offering/999999`;
const collegEInstitutionUserToken = await getInstitutionToken(
InstitutionTokenTypes.CollegeEReadOnlyUser,
);

// Act/Assert
await request(app.getHttpServer())
.patch(endpoint)
.auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.FORBIDDEN)
.expect({
statusCode: HttpStatus.FORBIDDEN,
message: "Forbidden resource",
error: "Forbidden",
});
});

it("Should throw error when education program is expired.", async () => {
// Arrange
const fakeEducationProgram = createFakeEducationProgram(
Expand Down
Loading
Loading