Skip to content

Commit

Permalink
test: add tests for concurrent revisions that discard form changes
Browse files Browse the repository at this point in the history
chore: run precommit on all files
  • Loading branch information
mikevespi committed Apr 2, 2024
1 parent f15e533 commit 1a2ac24
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 52 deletions.
3 changes: 2 additions & 1 deletion docs/concurrentRevisionHandling.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ An Amendment is opened on a project, and left open while it is being negotiated.
A solution that would allow us to handle concurrency without user input on conflict resolution was needed. To achieve this, the approach taken is comparable to a git rebase. When committing and pending are in conflict, the changes made in pending are applied on top of the committing form change, as if the committing `form_change` were the original parent of the pending `form_change`. While users commit on a `project_revision` level, the change propogates down to the `form_change` level, so when we're talking about this here it is at the `form_change` granularity, and the heart it takes place in the function `cif.commit_form_change_internal`.

One of the ways our various forms can be categorized would be:

- forms a project can have at most one of (`funding_parameter_EP`, `funding_parameter_IA`, `emission_intensity`, `project_summary_report`)
- 'project_contact' are either primary or secondary, and have a `contactIndex`
- 'project_manager' are categorized by `projectManagerLabelId`
- 'reporting_requirement' have a `reportingRequirementIndex` based on the `json_schema_name`

Form changes can have an operation of `create`, `update`, or `archive`, each of which need to be handled for all of the above categories. This results in several unique cases, which have been explained case-by-case using in-line in the `commit_form_change_internal` where they have more context.
Form changes can have an operation of `create`, `update`, or `archive`, each of which need to be handled for all of the above categories. This results in several unique cases, which have been explained case-by-case using in-line in the `commit_form_change_internal` where they have more context.

After each of the following cases, the `previous_form_change_id` of the pending `form_change` is set to be the id of the committing `form_change`, which leaves every form change with a `previous_form_change_id` of the **last commit** corresponding `form_change`, while preserving the option of a full history by maintaining accurate `created_at`, `updated_at`, and `archived_at` values for all `form_change`.

Expand Down
4 changes: 2 additions & 2 deletions schema/deploy/mutations/commit_form_change_internal.sql
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ begin
project_revision_id => pending_project_revision_id,
json_schema_name => fc.json_schema_name,
new_form_data => (fc.new_form_data || format('{"reportingRequirementIndex": %s}',
(select max((new_form_data ->> 'reportingRequirementIndex')::int) from cif.form_change
(select max((new_form_data ->> 'reportingRequirementIndex')::int) from cif.form_change
where project_revision_id=pending_project_revision_id
and json_schema_name = fc.json_schema_name
and new_form_data ->> 'reportType' = fc.new_form_data ->> 'reportType'
Expand Down Expand Up @@ -163,7 +163,7 @@ begin
project_revision_id => pending_project_revision_id,
json_schema_name => fc.json_schema_name,
new_form_data => (fc.new_form_data || format('{"reportingRequirementIndex": %s}',
(select max((new_form_data ->> 'reportingRequirementIndex')::int) from cif.form_change
(select max((new_form_data ->> 'reportingRequirementIndex')::int) from cif.form_change
where project_revision_id=pending_project_revision_id
and json_schema_name = fc.json_schema_name
) + 1)::jsonb
Expand Down

This file was deleted.

165 changes: 165 additions & 0 deletions schema/test/unit/concurrent_revisions/discards_test.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
begin;

select plan(7);


truncate table cif.project, cif.operator, cif.contact, cif.attachment restart identity cascade;
insert into cif.operator(legal_name) values ('test operator');
insert into cif.contact(given_name, family_name, email) values ('John', 'Test', '[email protected]'), ('Sandy', 'Olson', '[email protected]');
insert into cif.attachment (description, file_name, file_type, file_size)
values ('description1', 'file_name1', 'file_type1', 100), ('description2', 'file_name2', 'file_type1', 100);
insert into cif.cif_user(id, session_sub, given_name, family_name)
overriding system value
values (1, '11111111-1111-1111-1111-111111111111', 'Jan','Jansen'),
(2, '22222222-2222-2222-2222-222222222222', 'Max','Mustermann'),
(3, '33333333-3333-3333-3333-333333333333', 'Eva', 'Nováková');

-- Create a project to update.
select cif.create_project(1); -- id = 1
update cif.form_change set new_form_data='{
"projectName": "name",
"summary": "original (incorrect at point of test)",
"fundingStreamRfpId": 1,
"projectStatusId": 1,
"proposalReference": "1235",
"operatorId": 1
}'::jsonb
where project_revision_id=1
and form_data_table_name='project';

select cif.add_contact_to_revision(1, 1, 1);
select cif.add_contact_to_revision(1, 2, 2);
select cif.add_project_attachment_to_revision(1,1);
select cif.create_form_change(
'create',
'funding_parameter_EP',
'cif',
'funding_parameter',
json_build_object(
'projectId', 1,
'provinceSharePercentage', 1
)::jsonb,
null,
1
);
select cif.create_form_change(
'create',
'project_manager',
'cif',
'project_manager',
json_build_object(
'projectManagerLabelId', 1,
'cifUserId', 1,
'projectId', 1
)::jsonb,
null,
1
);
select cif.create_form_change(
'create',
'reporting_requirement',
'cif',
'reporting_requirement',
json_build_object(
'reportType', 'Quarterly',
'reportingRequirementIndex', 1,
'projectId', 1
)::jsonb,
null,
1
);
select cif.create_form_change(
'create',
'milestone',
'cif',
'reporting_requirement',
json_build_object(
'reportType', 'General Milestone',
'reportingRequirementIndex', 1
)::jsonb,
null,
1
);
select cif.commit_project_revision(1);

-- create the amendment that will be "pending"
select cif.create_project_revision(1, 'Amendment'); -- id = 2

-- create the general revision that will be "committing"
select cif.create_project_revision(1, 'General Revision'); -- id = 3

update cif.form_change set operation = 'archive'
where project_revision_id=3
and json_schema_name='project_contact'
and new_form_data ->> 'contactIndex' = '2';

update cif.form_change set operation = 'archive'
where project_revision_id=3
and json_schema_name='reporting_requirement'
and new_form_data ->> 'reportType' = 'Quarterly';

update cif.form_change set operation = 'archive'
where project_revision_id=3
and json_schema_name='project_manager';

update cif.form_change set operation = 'archive'
where project_revision_id=3
and json_schema_name='milestone';

select cif.discard_funding_parameter_form_change(3);

select cif.discard_project_attachment_form_change(
(select id from cif.form_change where project_revision_id = 3 and form_data_table_name = 'project_attachment')
);

select cif.commit_project_revision(3);

select is (
(select count(*) from cif.form_change where project_revision_id = 2 and json_schema_name = 'project_contact'),
1::bigint,
'When the committing form change archives a project contact, the corresponding form change in the pending revision on that project is deleted'
);

select is (
(select count(*) from cif.form_change where project_revision_id = 2 and json_schema_name = 'reporting_requirement' and new_form_data ->> 'reportType' = 'Quarterly'),
0::bigint,
'When the committing form change archives a quarterly report, the corresponding form change in the pending revision on that project is deleted'
);

select is (
(select count(*) from cif.form_change where project_revision_id = 2 and json_schema_name = 'project_manager'),
0::bigint,
'When the committing form change removes a project manager, the corresponding form change in the pending revision on that project is deleted'
);

select is (
(select count(*) from cif.form_change where project_revision_id = 2 and json_schema_name = 'funding_parameter_EP'),
0::bigint,
'When the committing form change discards the emission intensity report, the corresponding form change in the pending revision on that project is deleted'
);

select is (
(select count(*) from cif.form_change where project_revision_id = 2 and form_data_table_name = 'project_attachment'),
0::bigint,
'When the committing form change is discarding a project attachment, the pending fc is deleted.'
);

-- Commit the pending ammednment

select lives_ok (
$$
select cif.commit_project_revision(2)
$$,
'Committing the pending project_revision does not throw an error'
);

select is (
(select count(*) from cif.form_change where form_data_record_id is null),
0::bigint,
'All of the committed form_change records have a form_data_record_id assigned after pending is committed.'
);


select finish();

rollback;

0 comments on commit 1a2ac24

Please sign in to comment.