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

Jobs tests #1286

Merged
merged 46 commits into from
Jul 29, 2024
Merged

Jobs tests #1286

merged 46 commits into from
Jul 29, 2024

Conversation

sofyalaski
Copy link
Contributor

@sofyalaski sofyalaski commented Jun 26, 2024

Description

Implements API tests for jobs authorization endpoints and proposes fixes for missing cases

Motivation

Fixes are proposed to follow the authorization logic described in the documentation

Fixes:

jobs.controller.ts

  • use CreateJobAuth enum instead of AuthOp to define jobDatasetAuthorization
  • in instanceAuthorizationJobCreate shift an "if-block" checking the presence of ownerGroup down after the admin check was passed (reason: admin should be able to create a job for anonym)
  • in the same function, at the very beginning when initializing values of the JobClass instance add jobParams and leave ownerUser and ownerGroup undefined (for anonymous case)*
  • in the same function, check if the current authorization is in jobDatasetAuthorization (types mismatching)
  • in the same function, add a check if an unauthorized user creates a job for some other group (unauthorized users should only create jobs for themselves)
  • in the same function, for "dataset"-kind of job configurations, the "where" filtering wasn't grabbing the respective dataset values properly (maybe go back to Despina's implementation)
  • in the checkDatasetIds function add a check if each requested datasetId exists in database ( for the "dataset" -kind of job configurations)
  • implement generateJobInstanceForPermissions function, which is similar to the one in datasets.controller. mongoose findOne and findAll do not return an instance of JobClass as implemented but a simple JS Object. Since we still need to pass that object to casl( which expects the instance of JobClass) we need conversion. To not write all properties of JobClass, a minimal copy with only properties required for permission checking is created. The return instances are not affected by this and remain JS Object.
  • functionality of instanceAuthorizationJobStatusUpdate is now implemented in the patch endpoint function. This is done to avoid redefining a job return by findOne as a instance of JobClass with all properties.
  • for both GET endpoints only the endpoint authorisation was implemented. To add the instance authorization too, first create JobClass instance of the found job Objects (as described in two above items) and pass them to respective casl expressions.
  • adds implementation of delete endpoints. The rules are defined in the CheckPolicies decorator

jobs.service.ts

  • add a function to extract the username of a user sending a request ( should return "anonymous" if request sent by unauthenticated user). This is used both in create andstatusUpdate functions

casl-ability.factory.ts

  • for statusUpdate endpoint the user from update_job_groups had less rights that a normal user -> fixed that
  • add instance authorization for statusUpdate for anonymous user
  • implement delete endpoint authorization
  • fixed a negation typo

Changes

  • add deleteJobGroups to configuration.ts

Tests included

  • Included for each change/fix?
  • Passing? (Merge will not be approved unless this is checked)

sofyalaski and others added 29 commits May 8, 2024 08:55
* reset dataset before every api test

* Clear collections before each api test
1. Use CreateJobAuth enum instead of AuthOp to define jobDatasetAuthorization
2. Shift a block if groupOwner is present down after the admin check was passed (admin create a job for anonym)
3. Check if current authorization is in jobDatasetAuthorization in instanceAuthorizationJobCreate (types  mismatching)
4. Add a check if unauthorized user creates a job for someone else
if anonymous user creates a job, initialize a jwt instance for him/her
string array includes string enum
1. comment out jobConfig matchin, as now in interceptor
2. fix filtering object hierarchy
in #dataset kind of jobConfig's
3. not defining jobInstance ownerUser/Group as empty string
4. add jobInstance.jobParams initialization
5. add a check if datasets passed in the ID list exist
* Anonymous user can patch status update to a job in #all config. Added instance authorization

* Implement JobDelete authorization
* similarly to Datasets, mongoose returns an Object and not JobClass instances. To pass this into CASL we first need  to make those class instances. generateJobInstanceForPermissions implements a copy of object as correct class instance only with properties required in casl.

* functionality of instanceAuthorizationJobStatusUpdate is now implemented in the patch endpoint function. This is done to avoid redefining a found job as a instance of JobClass with all properties

* For both GET endpoints only the endpoint authorisation was implemented for user in the functions of the controller. To add the instance authorization too, first create JobClass instance of the found job Objects and pass them to respective casl expressions.

* adds implementation of delete endpoints. The rules are defined in the CheckPolicies decorator.
@despadam despadam requested a review from sbliven June 26, 2024 13:05
Copy link

@despadam despadam left a comment

Choose a reason for hiding this comment

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

Reviewed jobs.controller

src/jobs/jobs.controller.ts Outdated Show resolved Hide resolved
src/jobs/jobs.controller.ts Outdated Show resolved Hide resolved
src/jobs/jobs.controller.ts Outdated Show resolved Hide resolved
src/jobs/jobs.controller.ts Show resolved Hide resolved
src/jobs/jobs.controller.ts Show resolved Hide resolved
src/jobs/jobs.controller.ts Outdated Show resolved Hide resolved
src/jobs/jobs.controller.ts Show resolved Hide resolved
src/jobs/jobs.controller.ts Outdated Show resolved Hide resolved
@sofyalaski
Copy link
Contributor Author

@despadam I made some changes you requested. For other comments, we can discuss tomorrow in person

merge chanhes after conversation on how configuration will be stored
…e, st it can be used for all cases where we need to extract the configuration, without having to pass the full dto
@sofyalaski
Copy link
Contributor Author

@Despina, I've updated the change to configuration grabbing for statusUpdate as we discussed

sofyalaski and others added 2 commits July 14, 2024 22:11
- Run lint:fix
- Ignore no-explicit-any rule for jobParams (which are not type checked)
- remove 'read' jobOperation (not implemented)
test/config/pretest.js Outdated Show resolved Hide resolved
sbliven added 2 commits July 27, 2024 00:49
- Revert previous changes to ignore no-explicit-any
- Use `unknown` for jobParam inputs plus stronger runtime checks
- Fix compilation error in elastic-search service relating to
  deleting non-optional variables. 
- Remove rabbitmqaction validation. RabbitMQ should ignore the DTO body,
  and validate the config itself in the constructor.
Copy link
Contributor

@sbliven sbliven left a comment

Choose a reason for hiding this comment

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

This looks pretty good. I added a couple minor comments but it looks good.

Can you verify that mocha passes before merging? @despadam and I both ran into issues running the tests. Mocha should be added to the CI following this PR.

src/jobs/config/jobconfig.ts Outdated Show resolved Hide resolved
private body: Record<string, any> | null = null;

getActionType(): string {
return URLAction.actionType;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Copy link
Contributor

Choose a reason for hiding this comment

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

Weird that this empty method causes a lint warning; it overrides the interface method.

pid: { $eq: datasetIds[i] },
},
};
const idCount = await this.datasetsService.count(filter);
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't it be more performant to make a single database request here, rather than one request per datasetId?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, you're right, I changed that in the last commit

throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
message: "At least one dataset in the ids list doesn't exist.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Should include datasetIds[i] in the error message to make debugging easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in the last commit

src/jobs/jobs.controller.ts Show resolved Hide resolved
@@ -758,6 +866,7 @@ export class JobsController {
@Req() request: Request,
@Param("id") id: string,
): Promise<unknown> {
Logger.log("Deleting job!");
Copy link
Contributor

Choose a reason for hiding this comment

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

Should include the id in the log message

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in the last commit

password: TestData.Accounts["archiveManager"]["password"],
},
(tokenVal) => {
accessTokenArchiveManager = tokenVal;
Copy link
Contributor

Choose a reason for hiding this comment

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

That's a lot of nesting! Not necessary for this PR, but maybe getToken needs to be refactored to return a promise rather than use callbacks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one is addressed in PR #1350

test/Jobs.js Outdated
// res.body.should.not.have.property("id");
// res.body.should.have.propert("message").and.be.equal("Dataset ids list was not provided in jobParams");
// });
// });
Copy link
Contributor

Choose a reason for hiding this comment

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

Why commented?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, I was confused by the logic myself a bit. I changed this one into 0065. Now these tests at the beginning cover:

  • jobParams is passed with property datasetIds, but the array is empty --> fail (0040)
  • jobParams is passed with property datasetIds, but it refers to datasetIds that are not in database --> fail (0050)
  • jobParams is not passed at all --> fail (0060)
  • jobParams is passed as empty object --> fail (0065)

I additionally implemented the check for jobParams property in the create endpoint. It checks that jobParams is passed and is not empty. Before that if no jobParams were passed a 500 internal error was thrown. Now the error message is explicitly saying that jobParams need to be defined and not empty.

the last two tests are new. 0060 before was testing with an empty object as jobParams, which passed before. Now, as I I changed the implementation a little, this test fails. I think it is logical that if a job has no context it shouldn't be accepted? This test now is numbered as 0065.
0060 tests the case where no jobParams passed at all.

}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async validate(dto: T) {}

Choose a reason for hiding this comment

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

@sbliven I struggle to understand the need for validate() in the action class. I was always wondering whether it makes sense validating after calling the constructor, and your changes here I think prove my point that it probably doesn't. I would suggest following this example and moving all validation inside the constructor of each action. Then:

  • On job create if creating an action fails, the job creation should also fail.
  • On job statusUpdate things get a bit more complicated. Since we are using a global job configuration, instead of job specific, if no changes were made to the configuration then no validation is needed at all and we can proceed directly to performing the job. But, if the configuration has changed, we would need a method that would first update the values of the class' attributes, before performing the action.

Choose a reason for hiding this comment

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

After discussing with @sofyalaski, I will create a separate PR to address this.

0060 now checks if a job was passed without a required parameter JobParams
0065 (new) checks if a job was passed with an empty object for jobParams
1950 now tries to delete a job that doesn't exist and fails
1960 was called 1950 before
counts on GET are changed (because 0060 before was passing)
*  now makes one request to the db to find datasets and extracts their pids.
*  loggs the names of all dataset pids that are not in the db in case of an error
*  job endpoint implements the check that the jobParams were passed and are not empty, otherwise it will throw a bad request with respective message
*  job endpoint loggs the id of job to be deleted and implements a check if job exist in db, otherwise throws an error.
@despadam despadam merged commit 4e67d3a into SciCatProject:release-jobs Jul 29, 2024
7 checks passed
@sofyalaski
Copy link
Contributor Author

sofyalaski commented Jul 29, 2024

@sbliven in the commit two weeks ago I added running the tests on PR to the "relase-*" branches, and you can see the bars here at the bottom, I assume. For me, the API tests seem to be passing (API test include both test:api:jest and test:api:mocha. It might be because of some differences in the way we deploy then? We tried running the brand new docker container and running tests in it with Despina and it worked. Also, sorry for not telling that you need to start the app first!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants