Skip to content

Conversation

@andrewsignori-aot
Copy link
Collaborator

@andrewsignori-aot andrewsignori-aot commented Dec 12, 2025

  • Expose new env variables.
    • T4A_FOLDER, as planned, was removed from the constant and converted to an env config.
    • T4A_ARCHIVE_FOLDER was exposed to allow having all the archived files added to the same shared folder. Currently, all archive operations implicitly look for an "Archive" folder at the same level as the files being processed, which would not work for the T4A and its subfolder, unless an agreement is made to create them ahead of time (either manually or automatically), which is not desired.
  • File group adjusted to "Tax Documents".
  • Introduced a small helper to allow some level of identification of SSH issues. The initial goal is to detect a permanent issue where the processing of a T4A batch should be stopped because no further attempt will likely work.
  • Method renameFile from the SSH changed to allow using an existing SFTP client.
  • Method archiveFile changed to allow it to optionally receive the "absolute" path where the file should be archived.
  • Minor and basic SIN validation added to the SIN number received to ensure they are in the expected format, since the job can receive manual input.

E2E Tests

Queue processor for t4a-upload-enqueuer,
√ Should enqueue 3 batches when the batch size is 100, and there are 2 folders, one with 100 files and the other with 101 files.
√ Should log that no directories were found when no directories were listed from the SFTP.
√ Should not try to add bulk jobs when a directory was found from the SFTP, but no files were found.

Queue processor for t4a-upload,
√ Should download a T4A file from the SFTP, create a file in the student account, upload the file to S3 storage, queue a virus scan, send a notification to the student, and archive the processed file on success when the student has the current SIN valid, and matching the file name.
√ Should find a student match when the SIN is valid for one of the SIN validations entries when the student has multiple valid SIN numbers.
√ Should skip the file upload when a file with the same hash was already uploaded for the same student.
√ Should log a warning and archive the file with a not found student when an invalid SIN is found in the T4A file name.
√ Should log a warning and archive the file when multiple student accounts have the same valid SIN.

@andrewsignori-aot andrewsignori-aot changed the title Queue consumers t4 part 2 #2994 - T4A - Upload File to Student Account - Part 2 Dec 12, 2025
@andrewsignori-aot andrewsignori-aot self-assigned this Dec 12, 2025

This comment was marked as resolved.

@bcgov bcgov deleted a comment from Copilot AI Dec 16, 2025

This comment was marked as outdated.

* @returns True if the error is an SSH error with the given code, false otherwise.
*/
static hasError(error: unknown, errorCode: SSHErrorCodes): boolean {
return !!error && (error as SSHError).code === errorCode;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Was there a reason not to use the class and instance of similar to other errors here?

Copy link
Collaborator Author

@andrewsignori-aot andrewsignori-aot Dec 16, 2025

Choose a reason for hiding this comment

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

instance of works when a class is instantiated, similar to what we do in the Web for ApiProcessError.
Here, either the framework throws the error using a class, or the instance of would not work.
Please let me know if I am missing something.

Comment on lines +181 to +186
if (SshService.hasError(error, SSHErrorCodes.NotConnected)) {
// Stops processing as the SFTP client is disconnected.
throw new Error("SFTP client disconnected unexpectedly.", {
cause: error,
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

}
if (!students.length) {
processSummary.warn(
processSummary.info(
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

/**
* Validate if a string represents a valid UUID v4.
*/
export const uuidV4Matcher: jest.AsymmetricMatcher = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I didn't get the purpose of this matcher. Can you please help me understand?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It asserts if a string is a GUID and also checks the GUID version as 4 (the one that we have been using).
Instead of using an expected.any(String).

@tiago-graf tiago-graf self-requested a review December 16, 2025 22:19
Copy link
Collaborator

@dheepak-aot dheepak-aot left a comment

Choose a reason for hiding this comment

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

Great coverage of E2E tests. Thanks for making the changes. I would suggest to add the new envs to env-example file.

* @param errorCode Error code to be checked.
* @returns True if the error is an SSH error with the given code, false otherwise.
*/
static hasError(error: unknown, errorCode: SSHErrorCodes): boolean {
Copy link
Collaborator

Choose a reason for hiding this comment

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

name could be hasSSHError

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It can, but it is a static method, inside a service called SshService, that will be consumed as SshService.hasError. Does it really require SSH? If yes, then the createClient should be renamed to createSSHClient 😉

CRA_RESPONSE_FOLDER: ${{ secrets.CRA_RESPONSE_FOLDER }}
CRA_PROGRAM_AREA_CODE: ${{ secrets.CRA_PROGRAM_AREA_CODE }}
CRA_ENVIRONMENT_CODE: ${{ secrets.CRA_ENVIRONMENT_CODE }}
T4A_FOLDER: ${{ vars.T4A_FOLDER }}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please also update the devops makefile to pass these parameters.

Copy link
Collaborator

@tiago-graf tiago-graf left a comment

Choose a reason for hiding this comment

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

looks great, just a minor comment

@sonarqubecloud
Copy link

@github-actions
Copy link

Backend Unit Tests Coverage Report

Totals Coverage
Statements: 20.39% ( 4306 / 21120 )
Methods: 9.74% ( 252 / 2588 )
Lines: 24.53% ( 3688 / 15034 )
Branches: 10.46% ( 366 / 3498 )

@github-actions
Copy link

E2E Workflow Workers Coverage Report

Totals Coverage
Statements: 75.41% ( 1055 / 1399 )
Methods: 79.31% ( 115 / 145 )
Lines: 78.79% ( 769 / 976 )
Branches: 61.51% ( 171 / 278 )

@github-actions
Copy link

E2E Queue Consumers Coverage Report

Totals Coverage
Statements: 85.47% ( 1588 / 1858 )
Methods: 84.65% ( 182 / 215 )
Lines: 88.47% ( 1266 / 1431 )
Branches: 66.04% ( 140 / 212 )

@github-actions
Copy link

E2E SIMS API Coverage Report

Totals Coverage
Statements: 75.11% ( 8464 / 11269 )
Methods: 74.91% ( 1009 / 1347 )
Lines: 79.26% ( 6162 / 7774 )
Branches: 60.2% ( 1293 / 2148 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants