From eefe8d48015898338f3690ca5b26d5ec92e7ccbf Mon Sep 17 00:00:00 2001 From: Vincent Chalamon Date: Tue, 21 Apr 2020 19:04:10 +0200 Subject: [PATCH] Update Behat scenarios for user-system-refacto --- features/organization/assets.feature | 138 +++++++++++------- features/organization/children.feature | 89 +++++++---- features/organization/forecast.feature | 2 +- features/organization/home.feature | 41 ++++++ features/organization/login.feature | 27 ---- features/organization/mission_type.feature | 2 +- features/organization/planning.feature | 2 +- features/organization/search.feature | 79 +++++----- features/organization/users.feature | 126 +++++++++++----- .../user/{edit.feature => account.feature} | 13 +- features/user/availabilities.feature | 15 +- features/user/login.feature | 79 +++++++++- features/user/password.feature | 102 +++++++++++++ features/user/register.feature | 16 +- fixtures/users.yaml | 81 +++++++++- .../User/UserDeleteController.php | 2 + symfony.lock | 6 + 17 files changed, 600 insertions(+), 220 deletions(-) create mode 100644 features/organization/home.feature delete mode 100644 features/organization/login.feature rename features/user/{edit.feature => account.feature} (91%) create mode 100644 features/user/password.feature diff --git a/features/organization/assets.feature b/features/organization/assets.feature index b794d862..33dca05f 100644 --- a/features/organization/assets.feature +++ b/features/organization/assets.feature @@ -1,18 +1,18 @@ Feature: In order to manage the assets in my organization, - As an organization, + As an admin of an organization, I must be able to list, edit and delete assets in my organization. Scenario: As anonymous, I cannot list the assets from an organization When I go to "/organizations/201/assets" - Then I should be on "/organizations/login" + Then I should be on "/login" And the response status code should be 200 - Scenario: As an organization, I can list the assets from my organization - Given I am authenticated as "DT75" - And I am on "/organizations" + Scenario: As an admin of an organization, I can list the assets from my organization + Given I am authenticated as "john.doe@resop.com" + And I am on "/organizations/201" When I follow "Afficher la liste de mes véhicules" - Then I should be on "/organizations/201/assets/" + Then I should be on "/organizations/201/assets" And the response status code should be 200 And I should see "75992" And I should see "75996" @@ -20,14 +20,14 @@ Feature: And I should not see "7501" And I should not see "7710" - Scenario: As a parent organization, I can list the assets from my children organizations - Given I am authenticated as "DT75" - And I am on "/organizations" + Scenario: As an admin of a parent organization, I can list the assets from my children organizations + Given I am authenticated as "john.doe@resop.com" + And I am on "/organizations/201" When I follow "Modifier mes structures" - Then I should be on "/organizations/children" + Then I should be on "/organizations/201/children" And the response status code should be 200 When I follow "Liste des véhicules" - Then I should be on "/organizations/203/assets/" + Then I should be on "/organizations/201/assets?organization=203" And the response status code should be 200 And I should see "75012" And I should see "75016" @@ -35,111 +35,137 @@ Feature: And I should not see "7799" And I should not see "7710" - Scenario: As an organization, I cannot list the assets from an organization I don't have access to - Given I am authenticated as "DT75" + Scenario: As an admin of an organization, I cannot list the assets from an organization I don't have access to + Given I am authenticated as "john.doe@resop.com" When I go to "/organizations/202/assets" Then the response status code should be 403 - Scenario Outline: As an authenticated parent organization, I can add an asset on my organization or children organizations + Scenario: As an admin of a child organization, I cannot list the assets from the parent organization + Given I am authenticated as "jane.doe@resop.com" + When I go to "/organizations/201/assets" + Then the response status code should be 403 + + Scenario Outline: As an admin of an organization, I can add an asset on my organization or children organizations Given I am authenticated as "" - When I go to "/organizations/203/assets" + When I go to "" And I follow "Ajouter un nouveau véhicule" Then the response status code should be 200 - And I should be on "/organizations/203/assets/preAdd" + And I should be on "" When I select "VL" from "type" And I press "Continuer" Then the response status code should be 200 - And I should be on "/organizations/203/assets/add" + And I should be on "" When I fill in the following: - | commissionable_asset[name] | new vehicule | + | commissionable_asset[type] | VL | + | commissionable_asset[name] | new vehicule | + | commissionable_asset[hasMobileRadio] | 1 | + | commissionable_asset[hasFirstAidKit] | 1 | + | commissionable_asset[parkingLocation] | some parking location | + | commissionable_asset[contact] | some contact | + | commissionable_asset[seatingCapacity] | 5 | + | commissionable_asset[licensePlate] | some license plate | + | commissionable_asset[comments] | some comments | And I press "Enregistrer" + Then I should be on "" + And the response status code should be 200 And I should see "Véhicule créé" - And I should see "new vehicule" + And I should see "VL - new vehicule" + When I follow "Modifier" at position -1 + Then I should be on "/organizations/3/assets/1/edit" + And the response status code should be 200 + And the "commissionable_asset_type" field should contain "VL" + And the "commissionable_asset_name" field should contain "new vehicule" + And the "commissionable_asset_hasMobileRadio_0" checkbox is checked + And the "commissionable_asset_hasFirstAidKit_0" checkbox is checked + And the "commissionable_asset_parkingLocation" field should contain "some parking location" + And the "commissionable_asset_contact" field should contain "some contact" + And the "commissionable_asset_seatingCapacity" field should contain "5" + And the "commissionable_asset_licensePlate" field should contain "some license plate" + And the "commissionable_asset_comments" field should contain "some comments" Examples: - | login | + | login | list_url | preAdd_url | add_url | # todo: there is a bug when using parent organization: https://github.com/crf-devs/resop/issues/360 -# | DT75 | - | UL 01-02 | +# todo: how to create a new asset on a children organization (but not on current one)? +# | john.doe@resop.com | /organizations/201/assets?organization=203 | /organizations/201/assets/preAdd | /organizations/201/assets/add | + | jane.doe@resop.com | /organizations/203/assets | /organizations/203/assets/preAdd | /organizations/203/assets/add | # TODO Fix this test # @javascript -# Scenario: As an organization, I can display an asset modal -# Given I am authenticated as "DT75" -# When I go to "/organizations/203/assets" +# Scenario: As an admin of an organization, I can display an asset modal +# Given I am authenticated as "john.doe@resop.com" +# When I go to "/organizations/201/assets" # And I follow "Afficher" # And I wait for ".ajax-modal-content" to be visible # Then I should see "Modifier" # And I follow "Modifier" -# Then I should be on "/organizations/203/assets/75012/edit" +# Then I should be on "/organizations/201/assets/75012/edit" - Scenario Outline: As an organization, I can update an asset from my organization or children organizations + Scenario Outline: As an admin of an organization, I can update an asset from my organization or children organizations Given I am authenticated as "" - When I go to "/organizations/203/assets/75012/edit" - Then I should be on "/organizations/203/assets/75012/edit" + When I go to "" + Then I should be on "" And the response status code should be 200 And the "commissionable_asset_name" field should contain "75012" When I fill in the following: | commissionable_asset[name] | new name | And I press "Enregistrer" - Then I should be on "/organizations/203/assets/" + Then I should be on "" And the response status code should be 200 And I should see "Véhicule \"VPSP - new name\" mis à jour avec succès" - When I go to "/organizations/203/assets/75012/edit" + When I go to "" And the "commissionable_asset_name" field should contain "new name" Examples: - | login | + | login | edit_url | list_url | # todo: there is a bug when using parent organization: https://github.com/crf-devs/resop/issues/360 -# | DT75 | - | UL 01-02 | +# | john.doe@resop.com | /organizations/201/assets/75012/edit | /organizations/201/assets?organization=203 | + | jane.doe@resop.com | /organizations/203/assets/75012/edit | /organizations/203/assets | - Scenario: As a parent organization, I cannot update an asset from an organization I don't have access to - Given I am authenticated as "DT75" + Scenario: As an admin of a parent organization, I cannot update an asset from an organization I don't have access to + Given I am authenticated as "john.doe@resop.com" When I go to "/organizations/202/assets/77992/edit" Then the response status code should be 403 Scenario: As an admin of a child organization, I cannot update an asset on the parent organization - Given I am authenticated as "UL 01-02" + Given I am authenticated as "jane.doe@resop.com" When I go to "/organizations/201/assets/75992/edit" Then the response status code should be 403 - Scenario: As a parent organization, I cannot update an invalid asset - Given I am authenticated as "DT75" + Scenario: As an admin of a parent organization, I cannot update an invalid asset + Given I am authenticated as "john.doe@resop.com" When I go to "/organizations/201/assets/75012/edit" Then the response status code should be 404 - Scenario: As a parent organization, I can delete an asset from my organization or children organizations - Given I am on "/organizations/login" - When I select "DT75" from "identifier" - And I fill in "password" with "covid19" - And I press "Je me connecte" - Then I should be on "/organizations/" - @javascript - Scenario: As a parent organization, I can delete an asset from my organization or children organizations - Given I am authenticated as "DT75" - And I go to "/organizations/203/assets/75012/edit" + Scenario: As an admin of a parent organization, I can delete an asset from my organization or children organizations + Given I am authenticated as "john.doe@resop.com" + And I go to "/organizations/201/assets/75012/edit" When I follow "Supprimer" And I wait for "#delete-item-modal" to be visible Then I should see "Vous êtes sur le point de supprimer le véhicule suivant et toutes ses disponibilités : VPSP - 75012" When I press "Supprimer" - Then I should be on "/organizations/203/assets/" + Then I should be on "/organizations/201/assets?organization=203" And I should see "Le véhicule a été supprimé avec succès." And I should not see "75012" - #https://github.com/crf-devs/resop/issues/348 -# Scenario: As a parent organization, I cannot directly delete an asset from my organization +# https://github.com/crf-devs/resop/issues/348 +# Scenario: As an admin of a parent organization, I cannot directly delete an asset from my organization # Given I am authenticated as "john.doe@resop.com" # When I go to "/organizations/201/assets/75992/delete" # Then the response status code should be 405 - Scenario: As a parent organization, I cannot delete an asset from another organization - Given I am authenticated as "DT75" + Scenario: As an admin of a parent organization, I cannot delete an asset from another organization + Given I am authenticated as "john.doe@resop.com" When I go to "/organizations/202/assets" Then the response status code should be 403 When I go to "/organizations/202/assets/77992/delete" Then the response status code should be 403 - Scenario: As a parent organization, I cannot access availability of an invalid asset - Given I am authenticated as "DT75" + Scenario: As an admin of a parent organization, I cannot access availability of an invalid asset + Given I am authenticated as "john.doe@resop.com" + When I go to "/organizations/201/availability/75012/2020-W10" + Then the response status code should be 404 + + Scenario: As an admin of an organization, I cannot access availability of an asset with a mismatched url + Given I am authenticated as "john.doe@resop.com" When I go to "/organizations/201/availability/75012/2020-W10" Then the response status code should be 404 diff --git a/features/organization/children.feature b/features/organization/children.feature index 6f157e24..ae4e8f17 100644 --- a/features/organization/children.feature +++ b/features/organization/children.feature @@ -1,76 +1,101 @@ Feature: In order to manage my children organizations, - As a parent organization, + As an admin of a parent organization, I must be able to list, edit and create them. Scenario: As anonymous, I cannot list the children of an organization - When I go to "/organizations/children" - Then I should be on "/organizations/login" + When I go to "/organizations/201/children" + Then I should be on "/login" And the response status code should be 200 - Scenario: As a parent organization, I can list the children of my organization - Given I am authenticated as "DT75" - When I go to "/organizations" + Scenario: As an admin of a parent organization, I can list the children of my organization + Given I am authenticated as "john.doe@resop.com" + When I go to "/organizations/201" Then I should see "Modifier mes structures" When I follow "Modifier mes structures" - Then I should be on "/organizations/children" + Then I should be on "/organizations/201/children" And the response status code should be 200 And I should see "UL 01-02" - Scenario: As an organization without children, I cannot list the children of my organization - Given I am authenticated as "UL 01-02" + Scenario: As an admin of an organization without children, I cannot list the children of my organization + Given I am authenticated as "jane.doe@resop.com" When I go to "/organizations/203" Then I should not see "Modifier mes structures" - When I go to "/organizations/children" + When I go to "/organizations/203/children" And the response status code should be 403 + Scenario: As an admin of an organization but without a password, I cannot list the children of my organization + Given I am authenticated as "jane.doe@resop.com" + When I go to "/organizations/203/children" + Then the response status code should be 403 + + Scenario Outline: As an admin of an organization, I cannot list the children of another organization + Given I am authenticated as "john.doe@resop.com" + When I go to "" + Then the response status code should be 403 + Examples: + | url | + | /organizations/202/children | + | /organizations/203/children | + | /organizations/204/children | + Scenario: As anonymous, I cannot create an organization - When I go to "/organizations/new" - Then I should be on "/organizations/login" + When I go to "/organizations/201/new" + Then I should be on "/login" And the response status code should be 200 - Scenario: As a parent organization, I can create an organization - Given I am authenticated as "DT75" - And I am on "/organizations/children" + Scenario: As an admin of a parent organization, I can create an organization + Given I am authenticated as "john.doe@resop.com" + And I am on "/organizations/201/children" When I follow "Ajouter une structure" - Then I should be on "/organizations/new" + Then I should be on "/organizations/201/new" And the response status code should be 200 When I fill in the following: | organization[name] | Lorem ipsum | And I press "Valider" Then the response status code should be 200 - And I should be on "/organizations/children" + And I should be on "/organizations/201/children" And I should see "La structure a été ajoutée avec succès." And I should see "Lorem ipsum" - Scenario: As a children organization, I cannot create an organization - Given I am authenticated as "UL 01-02" + Scenario: As an admin of a children organization, I cannot create an organization + Given I am authenticated as "jane.doe@resop.com" When I go to "/organizations/203" Then I should not see "Modifier mes structures" - When I go to "/organizations/new" + When I go to "/organizations/203/new" Then the response status code should be 403 - Scenario: As anonymous, I cannot update an organization - When I go to "/organizations/201/edit" - Then I should be on "/organizations/login" - And the response status code should be 200 + Scenario: As an admin of an organization, I cannot create an organization on another one + Given I am authenticated as "john.doe@resop.com" + When I go to "/organizations/202/new" + Then the response status code should be 403 - Scenario: As a parent organization, I can update my children organizations - Given I am authenticated as "DT75" - And I am on "/organizations/children" + Scenario: As an admin of a parent organization, I can update my children organizations + Given I am authenticated as "john.doe@resop.com" + And I am on "/organizations/201/children" When I follow "Modifier" - Then I should be on "/organizations/203/edit" + Then I should be on "/organizations/201/children/203/edit" And the response status code should be 200 And I should see "Modifier une structure" When I fill in the following: | organization[name] | Lorem ipsum | And I press "Valider" - Then I should be on "/organizations/children" + Then I should be on "/organizations/201/children" And the response status code should be 200 And I should see "La structure a été mise à jour avec succès." And I should see "Lorem ipsum" - Scenario: As an organization, I cannot update an organization I don't have access to - Given I am authenticated as "DT75" - When I go to "/organizations/202/edit" + Scenario: As an admin of an organization, I cannot update an organization I don't have access to + Given I am authenticated as "john.doe@resop.com" + When I go to "/organizations/201/children/204/edit" + Then the response status code should be 403 + + Scenario: As anonymous, I cannot update an organization + When I go to "/organizations/201/children/203/edit" + Then I should be on "/login" + And the response status code should be 200 + + Scenario: As an admin of an organization, I cannot update my organization + Given I am authenticated as "jane.doe@resop.com" + When I go to "/organizations/201/children/203/edit" Then the response status code should be 403 diff --git a/features/organization/forecast.feature b/features/organization/forecast.feature index 3eedf521..0a6ea0a8 100644 --- a/features/organization/forecast.feature +++ b/features/organization/forecast.feature @@ -6,7 +6,7 @@ Feature: Scenario: As anonymous, I cannot access to the forecast page When I go to "/organizations/forecast" - Then I should be on "/organizations/login" + Then I should be on "/login" Scenario: As an authenticated children organization, I cannot use the forecast search form Given I am authenticated as "UL 01-02" diff --git a/features/organization/home.feature b/features/organization/home.feature new file mode 100644 index 00000000..7274b635 --- /dev/null +++ b/features/organization/home.feature @@ -0,0 +1,41 @@ +Feature: + In order to manage an organization, + As an admin of an organization, + I must be able to log in to my organization. + + Scenario: As anonymous, I cannot go to the homepage of an organization + When I go to "/organizations/1" + Then I should be on "/login" + And the response status code should be 200 + + Scenario: As an admin of an organization, I can go to the homepage of my organization + Given I am authenticated as "john.doe@resop.com" + And I am on the homepage + When I follow "Gérer ma structure" + Then I should be on "/organizations/1" + And the response status code should be 200 + And I should see "DT75" + + Scenario Outline: As an admin of an organization but without a password, I cannot go to any page of my organization + Given I am authenticated as "jane.doe@resop.com" + When I go to "" + Then the response status code should be 403 + Examples: + | url | + | /organizations/3 | + | /organizations/3/new | + | /organizations/3/search | + | /organizations/3/edit | + | /organizations/3/assets | + | /organizations/3/users | + | /organizations/planning | + + Scenario Outline: As an admin of an organization, I cannot go to the homepage of another organization + Given I am authenticated as "john.doe@resop.com" + When I go to "" + Then the response status code should be 403 + Examples: + | url | + | /organizations/2 | + | /organizations/3 | + | /organizations/4 | diff --git a/features/organization/login.feature b/features/organization/login.feature deleted file mode 100644 index f435f23f..00000000 --- a/features/organization/login.feature +++ /dev/null @@ -1,27 +0,0 @@ -@login -Feature: - In order to manage my actions, - As an organization, - I must be able to log in. - - Scenario: As anonymous, I cannot access to the homepage - When I go to "/organizations" - Then I should be on "/organizations/login" - - Scenario Outline: As a registered organization, I can log in - Given I am on "/organizations/login" - When I select "" from "identifier" - And I fill in "password" with "covid19" - And I press "Je me connecte" - Then I should be on "/organizations/" - And I should see "" - Examples: - | identifier | name | - | DT75 | DT75 | - | UL 01-02 | DT75 - UL 01-02 | - - Scenario: As an authenticated organization, I can log out - Given I am authenticated as "UL 01-02" - And I am on "/organizations/" - When I follow "Déconnexion" - Then I should be on "/organizations/login" diff --git a/features/organization/mission_type.feature b/features/organization/mission_type.feature index 382f68e0..23357b5d 100644 --- a/features/organization/mission_type.feature +++ b/features/organization/mission_type.feature @@ -6,7 +6,7 @@ Feature: Scenario: As anonymous, I cannot list mission types from an organization When I go to "/organizations/mission_type/new" - Then I should be on "/organizations/login" + Then I should be on "/login" And the response status code should be 200 Scenario: As an organization, I can list my mission types diff --git a/features/organization/planning.feature b/features/organization/planning.feature index 02cb2bac..b458a5bb 100644 --- a/features/organization/planning.feature +++ b/features/organization/planning.feature @@ -7,7 +7,7 @@ Feature: Scenario: As an anonymous, I cannot access the planning Given I go to "/organizations/planning" - Then I should be on "/organizations/login" + Then I should be on "/login" And the response status code should be 200 Scenario: As an organization, I have access to the planning and I can see my resources diff --git a/features/organization/search.feature b/features/organization/search.feature index c4547cf0..af5490d9 100644 --- a/features/organization/search.feature +++ b/features/organization/search.feature @@ -1,55 +1,56 @@ @search Feature: - In order to find a user or an asset - As an organization - I must be able to search users and assets + In order to find a user or an asset, + As an admin of an organization, + I must be able to search users and assets. - Scenario: As anonymous, I cannot access to the search page - When I go to "/organizations/search?query=foo" - Then I should be on "/organizations/login" + Scenario: As anonymous, I cannot go to the search page + When I go to "/organizations/1/search?query=foo" + Then I should be on "/login" + And the response status code should be 200 - Scenario Outline: As an authenticated parent organization, I can search for a user even in my children - Given I am authenticated as "DT75" - And I am on "/organizations/" - When I fill in "query" with " " + Scenario: As an admin of a parent organization, I can search for a volunteer even in my children organizations + Given I am authenticated as "john.doe@resop.com" + And I am on "/organizations/1" + When I fill in "query" with " jAnE dOe reSOp " And I press "Rechercher" - Then I should be on "/organizations/search" - And I should see "Rechercher \"\"" - And I should see "" - And I should see "" + Then I should be on "/organizations/1/search" + And the response status code should be 200 + And I should see "Rechercher \"jAnE dOe reSOp\"" + And I should see "990002A" + And I should see "jane.doe@resop.com" And I should see "Aucun véhicule ne correspond à votre recherche." - Examples: - | search | email | identificationNumber | - | jOhN dOe reSOp | john.doe@resop.com | 990001A | - | jAnE dOe reSOp | jane.doe@resop.com | 990002A | - Scenario: As an authenticated children organization, I can search for a user in my organization - Given I am authenticated as "UL 01-02" - And I am on "/organizations/" + Scenario: As an admin of a children organization, I can search for a volunteer in my organization + Given I am authenticated as "jane.doe@resop.com" + And I am on "/organizations/3" When I fill in "query" with " jAnE dOe reSOp " And I press "Rechercher" - Then I should be on "/organizations/search" + Then I should be on "/organizations/3/search" + And the response status code should be 200 And I should see "Rechercher \"jAnE dOe reSOp\"" And I should see "990002A" And I should see "jane.doe@resop.com" And I should see "Aucun véhicule ne correspond à votre recherche." - Scenario: As an authenticated organization, I cannot search for a user in another organization - Given I am authenticated as "UL 01-02" - And I am on "/organizations/" + Scenario: As an admin of an organization, I cannot search for a user in another organization + Given I am authenticated as "john.doe@resop.com" + And I am on "/organizations/1" When I fill in "query" with " cHuCk nOrRiS reSOp " And I press "Rechercher" - Then I should be on "/organizations/search" + Then I should be on "/organizations/3/search" + And the response status code should be 200 And I should see "Rechercher \"cHuCk nOrRiS reSOp\"" And I should see "Aucun bénévole ne correspond à votre recherche." And I should see "Aucun véhicule ne correspond à votre recherche." - Scenario Outline: As an authenticated parent organization, I can search for an asset even in my children - Given I am authenticated as "DT75" - And I am on "/organizations/" + Scenario Outline: As an admin of a parent organization, I can search for an asset even in my children + Given I am authenticated as "john.doe@resop.com" + And I am on "/organizations/1" When I fill in "query" with " " And I press "Rechercher" - Then I should be on "/organizations/search" + Then I should be on "/organizations/1/search" + And the response status code should be 200 And I should see "Rechercher \"\"" And I should see "" And I should see "" @@ -59,23 +60,25 @@ Feature: | 75992 | 75992 | VPSP | | 75012 | 75012 | VPSP | - Scenario: As an authenticated children organization, I can search for a user in my organization - Given I am authenticated as "UL 01-02" - And I am on "/organizations/" + Scenario: As an admin of a children organization, I can search for an asset in my organization + Given I am authenticated as "jane.doe@resop.com" + And I am on "/organizations/3" When I fill in "query" with " 75012 " And I press "Rechercher" - Then I should be on "/organizations/search" + Then I should be on "/organizations/3/search" + And the response status code should be 200 And I should see "Rechercher \"75012\"" And I should see "VPSP" And I should see "75012" And I should see "Aucun bénévole ne correspond à votre recherche." - Scenario: As an authenticated organization, I cannot search for a user in another organization - Given I am authenticated as "UL 01-02" - And I am on "/organizations/" + Scenario: As an admin of an organization, I cannot search for an asset in another organization + Given I am authenticated as "jane.doe@resop.com" + And I am on "/organizations/3" When I fill in "query" with " 77282 " And I press "Rechercher" - Then I should be on "/organizations/search" + Then I should be on "/organizations/3/search" + And the response status code should be 200 And I should see "Rechercher \"77282\"" And I should see "Aucun bénévole ne correspond à votre recherche." And I should see "Aucun véhicule ne correspond à votre recherche." diff --git a/features/organization/users.feature b/features/organization/users.feature index b1e2bc85..46310bb5 100644 --- a/features/organization/users.feature +++ b/features/organization/users.feature @@ -1,17 +1,17 @@ @users Feature: In order to manage the users in my organization, - As an organization, + As an admin of an organization, I must be able to list, edit and delete users in my organization. Scenario: As anonymous, I cannot list the users from an organization - When I go to "/organizations/1/users" - Then I should be on "/organizations/login" + When I go to "/organizations/201/users" + Then I should be on "/login" And the response status code should be 200 - Scenario: As an organization, I can list the users from my organization - Given I am authenticated as "DT75" - And I am on "/organizations" + Scenario: As an admin of an organization, I can list the users from my organization + Given I am authenticated as "john.doe@resop.com" + And I am on "/organizations/201" When I follow "Afficher la liste de mes bénévoles inscrits" Then I should be on "/organizations/201/users/" And the response status code should be 200 @@ -21,38 +21,42 @@ Feature: And I should not see "chuck.norris@resop.com" And I should not see "freddy.mercury@resop.com" - Scenario: As a parent organization, I can list the users from my children organizations - Given I am authenticated as "DT75" - And I am on "/organizations" + Scenario: As an admin of a parent organization, I can list the users from my children organizations + Given I am authenticated as "john.doe@resop.com" + And I am on "/organizations/201" When I follow "Modifier mes structures" - Then I should be on "/organizations/children" + Then I should be on "/organizations/201/children" And the response status code should be 200 When I follow "Liste des bénévoles" - Then I should be on "/organizations/203/users/" + Then I should be on "/organizations/201/users?organization=203" And the response status code should be 200 And I should see "jane.doe@resop.com" + And I should see "jill.doe@resop.com" And I should not see "john.doe@resop.com" And I should not see "chuck.norris@resop.com" + And I should not see "freddy.mercury@resop.com" Scenario: As an admin of an organization, I cannot list the users from another organization - Given I am authenticated as "DT75" + Given I am authenticated as "john.doe@resop.com" When I go to "/organizations/202/users" Then the response status code should be 403 # TODO Fix this test # @javascript -# Scenario: As an organization, I can display a user modal -# Given I am authenticated as "DT75" -# When I go to "/organizations/203/users" +# Scenario: As an admin of an organization, I can display a user modal +# Given I am authenticated as "john.doe@resop.com" +# When I go to "/organizations/201/users?organization=203" # And I follow "Afficher" # And I wait for ".ajax-modal-content" to be visible # Then I should see "Modifier" # And I follow "Modifier" -# Then I should be on "/organizations/203/users/102/edit" +# Then I should be on "/organizations/201/users/102/edit" - Scenario Outline: As an organization, I can update a user from my organization or children organizations + Scenario Outline: As an admin of an organization, I can update a user from my organization or children organizations Given I am authenticated as "" - When I go to "/organizations/203/users/102/edit" + When I go to "" + And I follow "Modifier" + Then I should be on "" And the response status code should be 200 And the "user_identificationNumber" field should contain "990002A" And the "user_emailAddress" field should contain "jane.doe@resop.com" @@ -64,55 +68,103 @@ Feature: | user[firstName] | John | | user[lastName] | BON JOVI | And I press "Valider" - Then I should be on "/organizations/203/users/" + Then I should be on "" And the response status code should be 200 And I should see "Les informations ont été mises à jour avec succès." - When I go to "/organizations/203/users/102/edit" - Then I should be on "/organizations/203/users/102/edit" + When I go to "" + Then I should be on "" And the response status code should be 200 And the "user_identificationNumber" field should contain "999999A" And the "user_emailAddress" field should contain "john.bon.jovi@resop.com" And the "user_firstName" field should contain "John" And the "user_lastName" field should contain "BON JOVI" Examples: - | login | - | DT75 | - | UL 01-02 | + | login | edit_url | list_url | + | john.doe@resop.com | /organizations/201/users/102/edit | /organizations/201/users?organization=203 | + | jane.doe@resop.com | /organizations/203/users/102/edit | /organizations/203/users | - Scenario: As an admin of an organization, I cannot update a user from another organizations - Given I am authenticated as "DT75" - When I go to "/organizations/204/users/103/edit" + Scenario Outline: As an admin of an organization, I cannot update a user from another organizations + Given I am authenticated as "john.doe@resop.com" + When I go to "" Then the response status code should be 403 + Examples: + | url | + | /organizations/201/users/103/edit | + | /organizations/204/users/103/edit | @javascript Scenario Outline: As an organization, I can delete a user from my organization or children organizations Given I am authenticated as "" - When I go to "/organizations/203/users/102/edit" + When I go to "" And I follow "Supprimer" And I wait for "#delete-item-modal" to be visible Then I should see "Vous êtes sur le point de supprimer le bénévole suivant et toutes ses disponibilités : Jane DOE ( 990002A )." When I press "Supprimer" - Then I should be on "/organizations/203/users/" + Then I should be on "" And I should see "Le bénévole a été supprimé avec succès." And I should not see "jill.doe@resop.com" Examples: - | login | - | DT75 | - | UL 01-02 | -# + | login | edit_url | list_url | + | john.doe@resop.com | /organizations/201/users/102/edit | /organizations/201/users?organization=203 | + | jane.doe@resop.com | /organizations/203/users/102/edit | /organizations/203/users | + # Scenario: As an admin of an organization, I cannot directly delete a user from my organization # Given I am authenticated as "john.doe@resop.com" -# When I go to "/organizations/3/users/3/delete" +# When I go to "/organizations/203/users/102/delete" # Then the response status code should be 405 -# + Scenario: As an admin of an organization, I cannot delete a user from another organization - Given I am authenticated as "DT75" + Given I am authenticated as "john.doe@resop.com" When I go to "/organizations/204/users" Then the response status code should be 403 When I go to "/organizations/204/users/103/delete" Then the response status code should be 403 Scenario: As an admin of an organization, I cannot access an invalid user - Given I am authenticated as "DT75" + Given I am authenticated as "john.doe@resop.com" When I go to "/organizations/201/users/102/edit" Then the response status code should be 404 + + Scenario: As an admin of an organization, I cannot access a user with a mismatched url + Given I am authenticated as "john.doe@resop.com" + When I go to "/organizations/1/users/2/edit" + Then the response status code should be 404 + + Scenario: As an admin of an organization, i can promote a user as admin of its organization and this user has admin privilege + Given I am authenticated as "freddy.mercury@resop.com" + When I go to "/organizations/4/users/5/edit" + And I follow "Promouvoir administrateur de UL DE BRIE ET CHANTEREINE" + Then I should be on "/organizations/4/users/5/edit" + And the response status code should be 200 + And I should see "L'utilisateur \"Chuck NORRIS\" a été promu administrateur de UL DE BRIE ET CHANTEREINE avec succès." + And I should see "Révoquer la fonction d'administrateur de UL DE BRIE ET CHANTEREINE" + Given I am authenticated as "chuck.norris@resop.com" + When I go to "/organizations/4" + Then the response status code should be 200 + + Scenario: As an admin of an organization, I can revoke user's admin privilege of its organization and this user doesn't have admin privilege anymore + Given I am authenticated as "lady.gaga@resop.com" + When I go to "/organizations/4/users/4/edit" + And I follow "Révoquer la fonction d'administrateur de UL DE BRIE ET CHANTEREINE" + Then I should be on "/organizations/4/users/4/edit" + And the response status code should be 200 + And I should see "Le privilège d'administrateur pour la structure UL-DE-BRIE-ET-CHANTEREINE de \"Freddy MERCURY\" a été révoquée avec succès." + And I should see "Promouvoir administrateur de UL DE BRIE ET CHANTEREINE" + Given I am authenticated as "freddy.mercury@resop.com" + When I go to "/organizations/4" + Then the response status code should be 403 + + Scenario: As an admin of an organization, i cannot promote admin a user who is already admin + Given I am authenticated as "lady.gaga@resop.com" + When I go to "/organizations/4/users/4/promote-admin" + Then the response status code should be 400 + + Scenario: As an admin of an organization, i cannot promote admin an user which does not belong to the organization + Given I am authenticated as "lady.gaga@resop.com" + When I go to "/organizations/4/users/1/promote-admin" + Then the response status code should be 400 + + Scenario: As an admin of an organization, i cannot revoke an user who is not an admin + Given I am authenticated as "lady.gaga@resop.com" + When I go to "/organizations/4/users/5/revoke-admin" + Then the response status code should be 400 diff --git a/features/user/edit.feature b/features/user/account.feature similarity index 91% rename from features/user/edit.feature rename to features/user/account.feature index 32dc6a7e..2f026f5c 100644 --- a/features/user/edit.feature +++ b/features/user/account.feature @@ -1,16 +1,19 @@ Feature: - In order to update my account - As a user - I must be able to edit my personal information + In order to update my account, + As a user, + I must be able to edit my personal information. Scenario: As anonymous, I cannot update an account When I go to "/user/edit" Then I should be on "/login" + And the response status code should be 200 Scenario: As a user, I can see my account Given I am authenticated as "john.doe@resop.com" When I go to "/user/edit" - Then the "user_identificationNumber" field should contain "990001A" + Then I should be on "/user/edit" + And the response status code should be 200 + And the "user_identificationNumber" field should contain "990001A" And the "user_emailAddress" field should contain "john.doe@resop.com" And the "user_firstName" field should contain "John" And the "user_lastName" field should contain "DOE" @@ -65,6 +68,7 @@ Feature: | user[phoneNumber] | | And I press "Valider" Then I should be on "/" + And the response status code should be 200 And I should see "Vos informations ont été mises à jour avec succès." When I follow "Déconnexion" And I fill in the following: @@ -74,6 +78,7 @@ Feature: | user_login[birthday][year] | 1990 | And I press "Je me connecte" Then I should be on "/" + And the response status code should be 200 And I should see "NIVOL : 899999A" Examples: | login | phoneNumber | diff --git a/features/user/availabilities.feature b/features/user/availabilities.feature index 3769e880..f9bf0e92 100644 --- a/features/user/availabilities.feature +++ b/features/user/availabilities.feature @@ -1,15 +1,16 @@ @availability Feature: - In order to fill in my availabilities - As a user - I must be able to create my account + In order to fill in my availabilities, + As a user, + I must be able to create my account. Scenario: As anonymous, I cannot go to user planning When I go to "/user/availability" Then I should be on "/login" + And the response status code should be 200 Scenario: As authenticated user, I can navigate through the planning weeks - Given I am authenticated as "john.doe@resop.com" + Given I am authenticated as "jane.doe@resop.com" And I am on "/" When I follow "Semaine prochaine" Then the url should match "/user/availability" @@ -19,7 +20,7 @@ Feature: And the response status code should be 200 Scenario Outline: As authenticated user, I cannot add an availability on a booked/locked slot - Given I am authenticated as "john.doe@resop.com" + Given I am authenticated as "jane.doe@resop.com" And I am on "/" When I follow "Semaine prochaine" Then the url should match "/user/availability" @@ -33,7 +34,7 @@ Feature: | next week monday 8 am | Scenario Outline: As authenticated user, I can add and remove an availability at any free slot in the future - Given I am authenticated as "john.doe@resop.com" + Given I am authenticated as "jane.doe@resop.com" And I am on "/" When I follow "Semaine prochaine" # TODO Check the full url /user/availability/2020-W18 @@ -69,7 +70,7 @@ Feature: | next week sunday 10pm | Scenario Outline: As authenticated user, I can remove an availability at any available slot in the future - Given I am authenticated as "john.doe@resop.com" + Given I am authenticated as "jane.doe@resop.com" And I am on "/" When I follow "Semaine prochaine" Then the url should match "/user/availability" diff --git a/features/user/login.feature b/features/user/login.feature index 502f2f68..121a6a86 100644 --- a/features/user/login.feature +++ b/features/user/login.feature @@ -1,13 +1,42 @@ Feature: - In order to fill in my availabilities - As a user - I must be able to log in + In order to fill in my availabilities, + As a user, + I must be able to log in. - Scenario: As anonymous, I cannot access to the homepage + Scenario: As anonymous, I cannot go to the homepage When I go to the homepage Then I should be on "/login" + And the response status code should be 200 - Scenario Outline: As a registered user, I can log in + Scenario Outline: As a user with a password, I can log in with it + Given I am on "/login" + When I fill in the following: + | user_login[identifier] | | + | user_login[password] | covid19 | + And I press "Je me connecte" + Then I should be on "/" + And the response status code should be 200 + And I should see "NIVOL : 990001A" + Examples: + | login | + | john.doe@resop.com | + | 990001A | + + Scenario Outline: As a user with a password, I cannot log in with an invalid one + Given I am on "/login" + When I fill in the following: + | user_login[identifier] | | + | user_login[password] | invalid | + And I press "Je me connecte" + Then I should be on "/login" + And the response status code should be 400 + And I should see "Veuillez saisir un numéro NIVOL ou une adresse e-mail valide, ou la date de naissance ne corresponds pas à ce NIVOL/email." + Examples: + | login | + | john.doe@resop.com | + | 990001A | + + Scenario Outline: As a user with a password, I cannot log in with my birth date Given I am on "/login" When I fill in the following: | user_login[identifier] | | @@ -15,18 +44,52 @@ Feature: | user_login[birthday][month] | 01 | | user_login[birthday][year] | 1990 | And I press "Je me connecte" - Then I should be on "/" - And I should see "NIVOL : 990001A" + Then I should be on "/login" + And the response status code should be 400 + And I should see "Veuillez saisir un numéro NIVOL ou une adresse e-mail valide, ou la date de naissance ne corresponds pas à ce NIVOL/email." Examples: | login | | john.doe@resop.com | | 990001A | + Scenario Outline: As a user without a password, I can log in with my birth date + Given I am on "/login" + When I fill in the following: + | user_login[identifier] | | + | user_login[birthday][day] | 01 | + | user_login[birthday][month] | 01 | + | user_login[birthday][year] | 1990 | + And I press "Je me connecte" + Then I should be on "/" + And the response status code should be 200 + And I should see "NIVOL : 990002A" + Examples: + | login | + | jane.doe@resop.com | + | 990001A | + + Scenario Outline: As a user without a password, I cannot log in with an invalid birth date + Given I am on "/login" + When I fill in the following: + | user_login[identifier] | | + | user_login[birthday][day] | 02 | + | user_login[birthday][month] | 02 | + | user_login[birthday][year] | 1992 | + And I press "Je me connecte" + Then I should be on "/login" + And the response status code should be 400 + And I should see "Veuillez saisir un numéro NIVOL ou une adresse e-mail valide, ou la date de naissance ne corresponds pas à ce NIVOL/email." + Examples: + | login | + | jane.doe@resop.com | + | 990001A | + Scenario: As an authenticated user, I can log out Given I am authenticated as "john.doe@resop.com" And I am on "/" When I follow "Déconnexion" Then I should be on "/login" + And the response status code should be 200 Scenario: As anonymous, when I try to log in with a non-existing account, I'm redirected to the registration page Given I am on "/login" @@ -37,9 +100,11 @@ Feature: | user_login[birthday][year] | 1990 | And I press "Je me connecte" Then I should be on "/user/new" + And the response status code should be 200 And the "user_emailAddress" field should contain "invalid@resop.com" Scenario: As anonymous, I cannot log in with empty data Given I am on "/login" When I press "Je me connecte" Then I should be on "/user/new" + And the response status code should be 200 diff --git a/features/user/password.feature b/features/user/password.feature new file mode 100644 index 00000000..7d35f9e6 --- /dev/null +++ b/features/user/password.feature @@ -0,0 +1,102 @@ +Feature: + In order to update my password, + As a user, + I must be able to set my password. + + Scenario: As anonymous, I cannot set a password + When I go to "/user/password" + Then I should be on "/login" + And the response status code should be 200 + + Scenario: As an admin of an organization with no password set, I see a warning + Given I am authenticated as "jane.doe@resop.com" + When I go to "/" + Then I should see "Vous devez renseigner votre mot de passe afin d'administrer votre structure." + And the response status code should be 400 + + Scenario: As a user, I cannot update my password with empty data + Given I am authenticated as "jane.doe@resop.com" + And I am on "/user/password" + When I fill in the following: + | password[password][first] | | + | password[password][second] | | + And I press "Valider" + Then I should be on "/user/password" + And the response status code should be 400 + And I should see "Cette valeur ne doit pas être vide." in the "label[for=password_password_first] .form-error-message" element + And I should see "Cette valeur ne doit pas être vide." in the "label[for=password_password_second] .form-error-message" element + + Scenario: As a user, I cannot update my password with invalid data + Given I am authenticated as "jane.doe@resop.com" + And I am on "/user/password" + When I fill in the following: + | password[password][first] | foo | + | password[password][second] | bar | + And I press "Valider" + Then I should be on "/user/password" + And the response status code should be 400 + And I should see "Les mots de passe ne correspondent pas." in the "label[for=password_password_first] .form-error-message" element + + Scenario Outline: As a user with a password, I cannot update it with an empty or invalid current one + Given I am authenticated as "john.doe@resop.com" + And I am on "/user/password" + When I fill in the following: + | password[current] | | + | password[password][first] | foo | + | password[password][second] | foo | + And I press "Valider" + Then I should be on "/user/password" + And the response status code should be 400 + And I should see "" in the "label[for=password_current] .form-error-message" element + Examples: + | value | message | + | | Cette valeur ne doit pas être vide. | + | invalid | Cette valeur est invalide. | + + Scenario Outline: As a user with a password, I can update it + Given I am authenticated as "john.doe@resop.com" + And I am on "/user/password" + When I fill in the following: + | password[current] | covid19 | + | password[password][first] | covid20 | + | password[password][second] | covid20 | + And I press "Valider" + Then I should be on "/" + And the response status code should be 200 + And I should see "Votre mot de passe a été mis à jour avec succès." + When I follow "Déconnexion" + And I fill in the following: + | user_login[identifier] | | + | user_login[password] | covid20 | + And I press "Je me connecte" + Then I should be on "/" + And the response status code should be 200 + And I should see "NIVOL : 990001A" + Examples: + | login | + | john.doe@resop.com | + | 990001A | + + Scenario Outline: As a user without a password, I can set it + Given I am authenticated as "jane.doe@resop.com" + And I am on "/user/password" + When I fill in the following: + | password[current] | | + | password[password][first] | covid20 | + | password[password][second] | covid20 | + And I press "Valider" + Then I should be on "/" + And the response status code should be 200 + And I should see "Votre mot de passe a été mis à jour avec succès." + When I follow "Déconnexion" + And I fill in the following: + | user_login[identifier] | | + | user_login[password] | covid20 | + And I press "Je me connecte" + Then I should be on "/" + And the response status code should be 200 + And I should see "NIVOL : 990002A" + Examples: + | login | + | jane.doe@resop.com | + | 990002A | diff --git a/features/user/register.feature b/features/user/register.feature index 06da263c..6a54033c 100644 --- a/features/user/register.feature +++ b/features/user/register.feature @@ -1,12 +1,13 @@ Feature: - In order to fill in my availabilities - As a user - I must be able to create my account + In order to fill in my availabilities, + As a user, + I must be able to create my account. Scenario: As authenticated user, I cannot create an account Given I am authenticated as "john.doe@resop.com" When I go to "/user/new" Then I should be on "/" + And the response status code should be 200 Scenario: As anonymous, I cannot create an account with already registered email address Given I am on "/user/new" @@ -19,8 +20,8 @@ Feature: And I select "UL 01-02" from "user[organization]" And I check "Maraudeur.se" And I press "Valider" - Then the response status code should be 400 - And I should be on "/user/new" + Then I should be on "/user/new" + And the response status code should be 400 And I should see "Cette valeur est déjà utilisée." Scenario: As anonymous, I cannot create an account with already registered identification number @@ -34,8 +35,8 @@ Feature: And I select "UL 01-02" from "user[organization]" And I check "Maraudeur.se" And I press "Valider" - Then the response status code should be 400 - And I should be on "/user/new" + Then I should be on "/user/new" + And the response status code should be 400 And I should see "Cette valeur est déjà utilisée." Scenario: As anonymous, I can create an account with valid data @@ -51,6 +52,7 @@ Feature: And I press "Valider" Then the response status code should be 200 And I should be on "/" + And the response status code should be 200 And I should see "Votre compte utilisateur a été créé avec succès." And I should see "Bienvenue, Archibald HADDOCK" And I should see "NIVOL : 999999A" diff --git a/fixtures/users.yaml b/fixtures/users.yaml index 8f311c69..16141af9 100644 --- a/fixtures/users.yaml +++ b/fixtures/users.yaml @@ -1,4 +1,6 @@ App\Entity\User: + # John DOE is admin of DT75. He is not volunteer. + # As organization admin, his password is required. User.john_doe: id: 101 firstName: John @@ -6,6 +8,7 @@ App\Entity\User: organization: '@Organization.DT75' identificationNumber: 990001A emailAddress (unique): john.doe@resop.com + plainPassword: covid19 phoneNumber: '' birthday: '1990-01-01' occupation: Pharmacien @@ -14,6 +17,11 @@ App\Entity\User: vulnerable: true fullyEquipped: true drivingLicence: true + organizations: ['@Organization.DT75'] + roles: ['ROLE_USER'] + + # Jane DOE is volunteer and admin of UL 01-02, an organization children of DT75 managed by John DOE. + # As organization admin, her password is required, but she didn't filled it yet. User.jane_doe: id: 102 firstName: Jane @@ -29,13 +37,81 @@ App\Entity\User: vulnerable: '' fullyEquipped: '' drivingLicence: '' + organizations: ['@Organization.UL-01-02'] + roles: ['ROLE_VOLUNTEER'] + + # Chuck NORRIS is volunteer in UL DE BRIE ET CHANTEREINE, an organization children of DT77 managed by Lady GAGA. + # Because he has a password, he has to connect using it. User.chuck_norris: - id: 103 + id: 105 firstName: Chuck lastName: NORRIS organization: '@Organization.UL-DE-BRIE-ET-CHANTEREINE' - identificationNumber: 990003A + identificationNumber: 990005A emailAddress (unique): chuck.norris@resop.com + plainPassword: covid19 + phoneNumber: '' + birthday: '1990-01-01' + occupation: '' + organizationOccupation: Secouriste + skillSet: '' + vulnerable: '' + fullyEquipped: '' + drivingLicence: '' + roles: ['ROLE_VOLUNTEER'] + + # Freddy MERCURY is volunteer in Organization.UL-DE-BRIE-ET-CHANTEREINE, and admin of UL DE BRIE ET CHANTEREINE, + # an organization children of DT77 managed Lady GAGA. + # As organization admin, his password is required. + User.freddy_mercury: + id: 104 + firstName: Freddy + lastName: MERCURY + organization: '@Organization.UL-DE-BRIE-ET-CHANTEREINE' + identificationNumber: 990004A + emailAddress (unique): freddy.mercury@resop.com + plainPassword: covid19 + phoneNumber: '' + birthday: '1990-01-01' + occupation: '' + organizationOccupation: Secouriste + skillSet: '' + vulnerable: '' + fullyEquipped: '' + drivingLicence: '' + organizations: ['@Organization.UL-DE-BRIE-ET-CHANTEREINE'] + roles: ['ROLE_VOLUNTEER'] + + # Lady GAGA is admin of DT77. She is not volunteer. + # As organization admin, her password is required. + User.lady.gaga: + id: 106 + firstName: Lady + lastName: GAGA + organization: '@Organization.DT77' + identificationNumber: 990006A + emailAddress (unique): lady.gaga@resop.com + plainPassword: covid19 + phoneNumber: '' + birthday: '1990-01-01' + occupation: '' + organizationOccupation: Secouriste + skillSet: '' + vulnerable: false + fullyEquipped: true + drivingLicence: true + organizations: ['@Organization.DT77'] + roles: ['ROLE_USER'] + + # Jill DOE is volunteer of UL 01-02, an organization children of DT75 managed by John DOE. + # As volunteer, she doesn't any password, and connects using her birth date. + User.jill_doe: + id: 103 + firstName: Jill + lastName: DOE + organization: '@Organization.UL-01-02' + identificationNumber: 990003A + emailAddress (unique): jill.doe@resop.com phoneNumber: '' birthday: '1990-01-01' occupation: '' @@ -44,3 +120,4 @@ App\Entity\User: vulnerable: '' fullyEquipped: '' drivingLicence: '' + roles: ['ROLE_VOLUNTEER'] diff --git a/src/Controller/Organization/User/UserDeleteController.php b/src/Controller/Organization/User/UserDeleteController.php index 11fe2e04..6de55f01 100644 --- a/src/Controller/Organization/User/UserDeleteController.php +++ b/src/Controller/Organization/User/UserDeleteController.php @@ -14,6 +14,8 @@ use Symfony\Component\Routing\Annotation\Route; /** + * todo Update the route to DELETE method for security reasons. + * * @Route("/{userToDelete<\d+>}/delete", name="app_user_delete", methods={"GET"}) * @IsGranted(UserVoter::CAN_EDIT, subject="userToDelete") */ diff --git a/symfony.lock b/symfony.lock index 20b411e7..b56e1ce8 100644 --- a/symfony.lock +++ b/symfony.lock @@ -8,6 +8,9 @@ "behat/gherkin": { "version": "v4.6.2" }, + "behat/mink-selenium2-driver": { + "version": "v1.4.0" + }, "behat/transliterator": { "version": "v1.3.0" }, @@ -188,6 +191,9 @@ "fixtures/.gitignore" ] }, + "instaclick/php-webdriver": { + "version": "1.4.7" + }, "jdorn/sql-formatter": { "version": "v1.2.17" },