describe API::V2::GraphqlController do let(:admin) { create(:administrateur) } let(:token) { admin.renew_api_token } let(:legacy_token) { APIToken.new(token).token } let(:procedure) { create(:procedure, :published, :for_individual, :with_service, administrateurs: [admin]) } let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } let(:dossier1) { create(:dossier, :en_construction, :with_individual, procedure: procedure, en_construction_at: 1.day.ago) } let(:dossier2) { create(:dossier, :en_construction, :with_individual, :archived, procedure: procedure, en_construction_at: 3.days.ago) } let(:dossiers) { [dossier] } let(:instructeur) { create(:instructeur, followed_dossiers: dossiers) } def compute_checksum_in_chunks(io) Digest::MD5.new.tap do |checksum| while (chunk = io.read(5.megabytes)) checksum << chunk end io.rewind end.base64digest end let(:file) { fixture_file_upload('spec/fixtures/files/logo_test_procedure.png', 'image/png') } let(:blob_info) do { filename: file.original_filename, byte_size: file.size, checksum: compute_checksum_in_chunks(file), content_type: file.content_type, # we don't want to run virus scanner on this file metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE } } end let(:blob) do blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_info) blob.upload(file) blob end before do instructeur.assign_to_procedure(procedure) end let(:query) do "{ demarche(number: #{procedure.id}) { id number title description state dateCreation dateDerniereModification dateFermeture groupeInstructeurs { label instructeurs { email } } revisions { id } draftRevision { id } publishedRevision { id champDescriptors { type } } service { nom typeOrganisme organisme } champDescriptors { id type label description required champDescriptors { id type } options } dossiers { nodes { id } } } }" end let(:variables) { {} } let(:operation_name) { nil } let(:query_id) { nil } let(:body) { JSON.parse(subject.body, symbolize_names: true) } let(:gql_data) { body[:data] } let(:gql_errors) { body[:errors] } subject { post :execute, params: { query: query, variables: variables, operationName: operation_name, queryId: query_id }.compact, as: :json } context "when authenticated with legacy token" do let(:authorization_header) { ActionController::HttpAuthentication::Token.encode_credentials(legacy_token) } before do request.env['HTTP_AUTHORIZATION'] = authorization_header end it "returns the demarche" do expect(gql_errors).to eq(nil) expect(gql_data[:demarche][:id]).to eq(procedure.to_typed_id) end end context "when authenticated" do let(:authorization_header) { ActionController::HttpAuthentication::Token.encode_credentials(token) } before do request.env['HTTP_AUTHORIZATION'] = authorization_header end describe "token authentication" do it { expect(gql_errors).to eq(nil) expect(gql_data).not_to be_nil } context "when the token is invalid" do before do request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Token.encode_credentials('invalid') end it { expect(gql_errors.first[:message]).to eq("An object of type Demarche was hidden due to permissions") } end context "when the token is revoked" do before do admin.update(encrypted_token: nil) end it { expect(token).not_to be_nil expect(gql_errors.first[:message]).to eq("An object of type Demarche was hidden due to permissions") } end end describe "demarche" do describe "query a demarche" do let(:procedure) { create(:procedure, :published, :for_individual, :with_service, :with_all_champs, :with_all_annotations, administrateurs: [admin]) } it "returns the demarche" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(demarche: { id: procedure.to_typed_id, number: procedure.id, title: procedure.libelle, description: procedure.description, state: 'publiee', dateFermeture: nil, dateCreation: procedure.created_at.iso8601, dateDerniereModification: procedure.updated_at.iso8601, groupeInstructeurs: [ { instructeurs: [{ email: instructeur.email }], label: "défaut" } ], revisions: procedure.revisions.map { |revision| { id: revision.to_typed_id } }, draftRevision: { id: procedure.draft_revision.to_typed_id }, publishedRevision: { id: procedure.published_revision.to_typed_id, champDescriptors: procedure.published_revision.types_de_champ_public.map do |tdc| { type: tdc.type_champ } end }, service: { nom: procedure.service.nom, typeOrganisme: procedure.service.type_organisme, organisme: procedure.service.organisme }, champDescriptors: procedure.active_revision.types_de_champ_public.map do |tdc| { id: tdc.to_typed_id, label: tdc.libelle, type: tdc.type_champ, description: tdc.description, required: tdc.mandatory?, champDescriptors: tdc.repetition? ? procedure.active_revision.children_of(tdc.reload).map { |tdc| { id: tdc.to_typed_id, type: tdc.type_champ } } : nil, options: tdc.drop_down_list? ? tdc.drop_down_list_options.reject(&:empty?) : nil } end, dossiers: { nodes: dossiers.map { |dossier| { id: dossier.to_typed_id } } } }) end end describe "filter dossiers" do let(:dossiers) { [dossier, dossier1, dossier2] } let(:query) do "{ demarche(number: #{procedure.id}) { id number dossiers(createdSince: \"#{2.days.ago.iso8601}\") { nodes { id } } } }" end it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(demarche: { id: procedure.to_typed_id, number: procedure.id, dossiers: { nodes: [{ id: dossier1.to_typed_id }, { id: dossier.to_typed_id }] } }) end end describe "filter archived dossiers" do let(:dossiers) { [dossier, dossier1, dossier2] } let(:query) do "{ demarche(number: #{procedure.id}) { id number dossiers(archived: #{archived_filter}) { nodes { id } } } }" end context 'with archived=true' do let(:archived_filter) { 'true' } it 'returns only archived dossiers' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(demarche: { id: procedure.to_typed_id, number: procedure.id, dossiers: { nodes: [{ id: dossier2.to_typed_id }] } }) end end context 'with archived=false' do let(:archived_filter) { 'false' } it 'returns only non-archived dossiers' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(demarche: { id: procedure.to_typed_id, number: procedure.id, dossiers: { nodes: [{ id: dossier1.to_typed_id }, { id: dossier.to_typed_id }] } }) end end end describe "filter by minRevision" do let(:query) do "{ demarche(number: #{procedure.id}) { id number dossiers(minRevision: \"#{procedure.revisions.first.to_typed_id}\") { nodes { id } } } }" end it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(demarche: { id: procedure.to_typed_id, number: procedure.id, dossiers: { nodes: procedure.dossiers.order(:created_at).map do |dossier| { id: dossier.to_typed_id } end } }) end end describe "filter by maxRevision" do let(:query) do "{ demarche(number: #{procedure.id}) { id number dossiers(maxRevision: \"#{procedure.revisions.last.to_typed_id}\") { nodes { id } } } }" end it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(demarche: { id: procedure.to_typed_id, number: procedure.id, dossiers: { nodes: procedure.dossiers.order(:created_at).map do |dossier| { id: dossier.to_typed_id } end } }) end end end describe "dossier" do let(:dossier) do dossier = create(:dossier, :en_construction, :with_populated_champs, :with_populated_annotations, :with_individual, procedure: procedure) create(:commentaire, :with_file, dossier: dossier, email: 'test@test.com') dossier end context "for individual" do let(:procedure) { create(:procedure, :published, :for_individual, :with_service, :with_all_champs, :with_all_annotations, administrateurs: [admin]) } let(:query) do "{ dossier(number: #{dossier.id}) { id number state dateDerniereModification datePassageEnConstruction datePassageEnInstruction dateTraitement dateDepot motivation motivationAttachment { url } demarche { number title state } usager { id email } demandeur { id ... on PersonnePhysique { nom prenom civilite dateDeNaissance } } instructeurs { id email } groupeInstructeur { id number label } revision { id champDescriptors { type } } messages { email body attachment { filename checksum byteSize contentType } } avis { expert { email } question reponse dateQuestion dateReponse attachment { url filename } } champs { id label stringValue } } }" end it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossier: { id: dossier.to_typed_id, number: dossier.id, state: 'en_construction', dateDerniereModification: dossier.updated_at.iso8601, datePassageEnConstruction: dossier.en_construction_at.iso8601, dateDepot: dossier.depose_at.iso8601, datePassageEnInstruction: nil, dateTraitement: nil, motivation: nil, motivationAttachment: nil, demarche: { number: dossier.procedure.id, title: dossier.procedure.libelle, state: 'publiee' }, usager: { id: dossier.user.to_typed_id, email: dossier.user.email }, instructeurs: [ { id: instructeur.to_typed_id, email: instructeur.email } ], groupeInstructeur: { id: dossier.groupe_instructeur.to_typed_id, number: dossier.groupe_instructeur.id, label: dossier.groupe_instructeur.label }, revision: { id: dossier.revision.to_typed_id, champDescriptors: dossier.types_de_champ.map do |tdc| { type: tdc.type_champ } end }, demandeur: { id: dossier.individual.to_typed_id, nom: dossier.individual.nom, prenom: dossier.individual.prenom, civilite: 'M', dateDeNaissance: '1991-11-01' }, messages: dossier.commentaires.map do |commentaire| { body: commentaire.body, attachment: { filename: commentaire.piece_jointe.filename.to_s, contentType: commentaire.piece_jointe.content_type, checksum: commentaire.piece_jointe.checksum, byteSize: commentaire.piece_jointe.byte_size }, email: commentaire.email } end, avis: [], champs: dossier.champs_public.map do |champ| { id: champ.to_typed_id, label: champ.libelle, stringValue: champ.for_api_v2 } end }) expect(gql_data[:dossier][:champs][0][:id]).to eq(dossier.champs_public[0].type_de_champ.to_typed_id) end end context "for entreprise" do let(:procedure_for_entreprise) { create(:procedure, :published, administrateurs: [admin]) } let(:dossier) { create(:dossier, :en_construction, :with_entreprise, procedure: procedure_for_entreprise) } let(:query) do "{ dossier(number: #{dossier.id}) { id number usager { id email } demandeur { id ... on PersonneMorale { siret siegeSocial numeroVoie typeVoie entreprise { siren dateCreation capitalSocial codeEffectifEntreprise numeroTvaIntracommunautaire } } } } }" end context "in the nominal case" do it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossier: { id: dossier.to_typed_id, number: dossier.id, usager: { id: dossier.user.to_typed_id, email: dossier.user.email }, demandeur: { id: dossier.etablissement.to_typed_id, siret: dossier.etablissement.siret, siegeSocial: dossier.etablissement.siege_social, numeroVoie: dossier.etablissement.numero_voie.to_s, typeVoie: dossier.etablissement.type_voie.to_s, entreprise: { siren: dossier.etablissement.entreprise_siren, dateCreation: dossier.etablissement.entreprise_date_creation.iso8601, capitalSocial: dossier.etablissement.entreprise_capital_social.to_s, codeEffectifEntreprise: dossier.etablissement.entreprise_code_effectif_entreprise.to_s, numeroTvaIntracommunautaire: dossier.etablissement.entreprise_numero_tva_intracommunautaire } } }) end end context "with links" do let(:dossier) { create(:dossier, :accepte, :with_attestation, procedure: procedure) } let(:query) do "{ dossier(number: #{dossier.id}) { id number pdf { url } geojson { url } attestation { url } } }" end it "urls should be returned" do expect(gql_errors).to eq(nil) expect(gql_data[:dossier][:pdf][:url]).not_to be_nil expect(gql_data[:dossier][:geojson][:url]).not_to be_nil expect(gql_data[:dossier][:attestation][:url]).not_to be_nil end end context "when there are missing data" do before do dossier.etablissement.update!(entreprise_code_effectif_entreprise: nil, entreprise_capital_social: nil, numero_voie: nil, type_voie: nil, entreprise_numero_tva_intracommunautaire: nil) end it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossier: { id: dossier.to_typed_id, number: dossier.id, usager: { id: dossier.user.to_typed_id, email: dossier.user.email }, demandeur: { id: dossier.etablissement.to_typed_id, siret: dossier.etablissement.siret, siegeSocial: dossier.etablissement.siege_social, numeroVoie: nil, typeVoie: nil, entreprise: { siren: dossier.etablissement.entreprise_siren, dateCreation: dossier.etablissement.entreprise_date_creation.iso8601, capitalSocial: '-1', codeEffectifEntreprise: nil, numeroTvaIntracommunautaire: nil } } }) end end end context "champs" do let(:procedure) { create(:procedure, :published, :for_individual, administrateurs: [admin], types_de_champ_public: [{ type: :date }, { type: :datetime }]) } let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } let(:champ_date) { dossier.champs_public.first } let(:champ_datetime) { dossier.champs_public.second } before do champ_date.update(value: '2019-07-10') champ_datetime.update(value: '15/09/1962 15:35') end context "with Date" do let(:query) do "{ dossier(number: #{dossier.id}) { champs { id label ... on DateChamp { value } } } }" end it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossier: { champs: [ { id: champ_date.to_typed_id, label: champ_date.libelle, value: '2019-07-10T00:00:00+02:00' }, { id: champ_datetime.to_typed_id, label: champ_datetime.libelle, value: '1962-09-15T15:35:00+01:00' } ] }) end end context "with Datetime" do let(:query) do "{ dossier(number: #{dossier.id}) { champs { id label ... on DateChamp { value date } ... on DatetimeChamp { datetime } } } }" end it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossier: { champs: [ { id: champ_date.to_typed_id, label: champ_date.libelle, value: '2019-07-10T00:00:00+02:00', date: '2019-07-10' }, { id: champ_datetime.to_typed_id, label: champ_datetime.libelle, datetime: '1962-09-15T15:35:00+01:00' } ] }) end end end end describe "deletedDossiers" do let(:query) do "{ demarche(number: #{procedure.id}) { deletedDossiers { nodes { id number state reason dateSupression } } } }" end let(:deleted_dossier) { create(:deleted_dossier, procedure: procedure) } before { deleted_dossier } it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(demarche: { deletedDossiers: { nodes: [ { id: deleted_dossier.to_typed_id, number: deleted_dossier.dossier_id, state: deleted_dossier.state, reason: deleted_dossier.reason, dateSupression: deleted_dossier.deleted_at.iso8601 } ] } }) end end describe "champ" do let(:champ) { create(:champ_piece_justificative, dossier: dossier) } let(:byte_size) { 2712286911 } context "byteSize" do let(:query) do "{ dossier(number: #{dossier.id}) { champs(id: \"#{champ.to_typed_id}\") { ... on PieceJustificativeChamp { file { byteSize } } } } }" end it { expect(gql_errors).to be_nil expect(gql_data).to eq(dossier: { champs: [{ file: { byteSize: 4 } }] }) } end context "when the file is really big" do before do champ.piece_justificative_file.blob.update(byte_size: byte_size) end context "byteSize" do let(:query) do "{ dossier(number: #{dossier.id}) { champs(id: \"#{champ.to_typed_id}\") { ... on PieceJustificativeChamp { file { byteSize } } } } }" end it { expect(gql_errors).not_to be_nil } end context "byteSizeBigInt" do let(:query) do "{ dossier(number: #{dossier.id}) { champs(id: \"#{champ.to_typed_id}\") { ... on PieceJustificativeChamp { file { byteSizeBigInt } } } } }" end it { expect(gql_errors).to be_nil expect(gql_data).to eq(dossier: { champs: [{ file: { byteSizeBigInt: '2712286911' } }] }) } end end end describe "groupeInstructeur" do let(:groupe_instructeur) { procedure.groupe_instructeurs.first } let(:query) do "{ groupeInstructeur(number: #{groupe_instructeur.id}) { id number label dossiers { nodes { id } } } }" end it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(groupeInstructeur: { id: groupe_instructeur.to_typed_id, number: groupe_instructeur.id, label: groupe_instructeur.label, dossiers: { nodes: dossiers.map { |dossier| { id: dossier.to_typed_id } } } }) end end describe 'stored queries' do let(:procedure) { create(:procedure, :published, :for_individual, administrateurs: [admin]) } let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } let(:query_id) { 'ds-query-v2' } context 'not found operation id' do let(:query_id) { 'ds-query-v0' } it { expect(gql_errors.first[:message]).to eq('No query with id "ds-query-v0"') } end context 'not found operation name' do let(:operation_name) { 'getStuff' } it { expect(gql_errors.first[:message]).to eq('No operation named "getStuff"') } end context 'getDossier' do let(:variables) { { dossierNumber: dossier.id } } let(:operation_name) { 'getDossier' } it { expect(gql_errors).to be_nil expect(gql_data[:dossier][:id]).to eq(dossier.to_typed_id) } end context 'getDemarche' do let(:variables) { { demarcheNumber: procedure.id } } let(:operation_name) { 'getDemarche' } before { dossier } it { expect(gql_errors).to be_nil expect(gql_data[:demarche][:id]).to eq(procedure.to_typed_id) expect(gql_data[:demarche][:dossiers]).to be_nil } context 'include Dossiers' do let(:variables) { { demarcheNumber: procedure.id, includeDossiers: true } } it { expect(gql_errors).to be_nil expect(gql_data[:demarche][:id]).to eq(procedure.to_typed_id) expect(gql_data[:demarche][:dossiers][:nodes].size).to eq(1) } end end context 'getGroupeInstructeur' do let(:groupe_instructeur) { procedure.groupe_instructeurs.first } let(:variables) { { groupeInstructeurNumber: groupe_instructeur.id } } let(:operation_name) { 'getGroupeInstructeur' } before { dossier } it { expect(gql_errors).to be_nil expect(gql_data[:groupeInstructeur][:id]).to eq(groupe_instructeur.to_typed_id) expect(gql_data[:groupeInstructeur][:dossiers]).to be_nil } context 'include Dossiers' do let(:variables) { { groupeInstructeurNumber: groupe_instructeur.id, includeDossiers: true } } it { expect(gql_errors).to be_nil expect(gql_data[:groupeInstructeur][:id]).to eq(groupe_instructeur.to_typed_id) expect(gql_data[:groupeInstructeur][:dossiers][:nodes].size).to eq(1) } end end context 'getDemarcheDescriptor' do let(:operation_name) { 'getDemarcheDescriptor' } context 'find by number' do let(:variables) { { demarche: { number: procedure.id } } } it { expect(gql_errors).to be_nil expect(gql_data[:demarcheDescriptor][:id]).to eq(procedure.to_typed_id) } end context 'find by id' do let(:variables) { { demarche: { id: procedure.to_typed_id } } } it { expect(gql_errors).to be_nil expect(gql_data[:demarcheDescriptor][:id]).to eq(procedure.to_typed_id) } end context 'not opendata' do let(:variables) { { demarche: { id: procedure.to_typed_id } } } before { procedure.update(opendata: false) } it { expect(gql_errors).to be_nil expect(gql_data[:demarcheDescriptor][:id]).to eq(procedure.to_typed_id) } end context 'without authorization token' do let(:authorization_header) { nil } context 'opendata' do let(:variables) { { demarche: { id: procedure.to_typed_id } } } it { expect(gql_errors).to be_nil expect(gql_data[:demarcheDescriptor][:id]).to eq(procedure.to_typed_id) } end context 'not opendata' do let(:variables) { { demarche: { id: procedure.to_typed_id } } } before { procedure.update(opendata: false) } it { expect(gql_errors).not_to be_nil expect(gql_errors.first[:message]).to eq('An object of type DemarcheDescriptor was hidden due to permissions') } end end end context 'mutation' do let(:query_id) { 'ds-mutation-v2' } context 'not found operation name' do let(:operation_name) { 'dossierStuff' } it { expect(gql_errors.first[:message]).to eq('No operation named "dossierStuff"') } end context 'dossierArchiver' do let(:dossier) { create(:dossier, :refuse, :with_individual, procedure: procedure) } let(:variables) { { input: { dossierId: dossier.to_typed_id, instructeurId: instructeur.to_typed_id } } } let(:operation_name) { 'dossierArchiver' } it { expect(gql_errors).to be_nil expect(gql_data[:dossierArchiver][:errors]).to be_nil expect(gql_data[:dossierArchiver][:dossier][:id]).to eq(dossier.to_typed_id) expect(gql_data[:dossierArchiver][:dossier][:archived]).to be_truthy } end context 'dossierPasserEnInstruction' do let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } let(:variables) { { input: { dossierId: dossier.to_typed_id, instructeurId: instructeur.to_typed_id } } } let(:operation_name) { 'dossierPasserEnInstruction' } it { expect(gql_errors).to be_nil expect(gql_data[:dossierPasserEnInstruction][:errors]).to be_nil expect(gql_data[:dossierPasserEnInstruction][:dossier][:id]).to eq(dossier.to_typed_id) expect(gql_data[:dossierPasserEnInstruction][:dossier][:state]).to eq('en_instruction') } end context 'dossierRepasserEnConstruction' do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:variables) { { input: { dossierId: dossier.to_typed_id, instructeurId: instructeur.to_typed_id } } } let(:operation_name) { 'dossierRepasserEnConstruction' } it { expect(gql_errors).to be_nil expect(gql_data[:dossierRepasserEnConstruction][:errors]).to be_nil expect(gql_data[:dossierRepasserEnConstruction][:dossier][:id]).to eq(dossier.to_typed_id) expect(gql_data[:dossierRepasserEnConstruction][:dossier][:state]).to eq('en_construction') } end context 'dossierRepasserEnInstruction' do let(:dossier) { create(:dossier, :refuse, :with_individual, procedure: procedure) } let(:variables) { { input: { dossierId: dossier.to_typed_id, instructeurId: instructeur.to_typed_id } } } let(:operation_name) { 'dossierRepasserEnInstruction' } it { expect(gql_errors).to be_nil expect(gql_data[:dossierRepasserEnInstruction][:errors]).to be_nil expect(gql_data[:dossierRepasserEnInstruction][:dossier][:id]).to eq(dossier.to_typed_id) expect(gql_data[:dossierRepasserEnInstruction][:dossier][:state]).to eq('en_instruction') } end context 'dossierAccepter' do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:variables) { { input: { dossierId: dossier.to_typed_id, instructeurId: instructeur.to_typed_id } } } let(:operation_name) { 'dossierAccepter' } it { expect(gql_errors).to be_nil expect(gql_data[:dossierAccepter][:errors]).to be_nil expect(gql_data[:dossierAccepter][:dossier][:id]).to eq(dossier.to_typed_id) expect(gql_data[:dossierAccepter][:dossier][:state]).to eq('accepte') } end context 'dossierRefuser' do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:variables) { { input: { dossierId: dossier.to_typed_id, instructeurId: instructeur.to_typed_id, motivation: 'yolo' } } } let(:operation_name) { 'dossierRefuser' } it { expect(gql_errors).to be_nil expect(gql_data[:dossierRefuser][:errors]).to be_nil expect(gql_data[:dossierRefuser][:dossier][:id]).to eq(dossier.to_typed_id) expect(gql_data[:dossierRefuser][:dossier][:state]).to eq('refuse') } end context 'dossierClasserSansSuite' do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:variables) { { input: { dossierId: dossier.to_typed_id, instructeurId: instructeur.to_typed_id, motivation: 'yolo' } } } let(:operation_name) { 'dossierClasserSansSuite' } it { expect(gql_errors).to be_nil expect(gql_data[:dossierClasserSansSuite][:errors]).to be_nil expect(gql_data[:dossierClasserSansSuite][:dossier][:id]).to eq(dossier.to_typed_id) expect(gql_data[:dossierClasserSansSuite][:dossier][:state]).to eq('sans_suite') } end context 'groupeInstructeurModifier' do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:variables) { { input: { groupeInstructeurId: dossier.groupe_instructeur.to_typed_id, label: 'nouveau groupe instructeur' } } } let(:operation_name) { 'groupeInstructeurModifier' } it { expect(gql_errors).to be_nil expect(gql_data[:groupeInstructeurModifier][:errors]).to be_nil expect(gql_data[:groupeInstructeurModifier][:groupeInstructeur][:id]).to eq(dossier.groupe_instructeur.to_typed_id) expect(dossier.groupe_instructeur.reload.label).to eq('nouveau groupe instructeur') } context 'close groupe instructeur' do let(:variables) { { input: { groupeInstructeurId: dossier.groupe_instructeur.to_typed_id, closed: true } } } context 'with multiple groupes' do before do create(:groupe_instructeur, procedure: procedure) end it { expect(gql_errors).to be_nil expect(gql_data[:groupeInstructeurModifier][:errors]).to be_nil expect(gql_data[:groupeInstructeurModifier][:groupeInstructeur][:id]).to eq(dossier.groupe_instructeur.to_typed_id) expect(dossier.groupe_instructeur.reload.closed).to be_truthy } end context 'validation error' do it { expect(gql_errors).to be_nil expect(gql_data[:groupeInstructeurModifier][:errors].first[:message]).to eq('Il doit y avoir au moins un groupe instructeur actif sur chaque démarche') } end end end end end describe "mutations" do describe 'dossierEnvoyerMessage' do let(:query) do "mutation($input: DossierEnvoyerMessageInput!) { dossierEnvoyerMessage(input: $input) { message { body } errors { message } } }" end let(:variables) { { input: input } } let(:input) do { dossierId: dossier_id, instructeurId: instructeur_id, body: input_body, attachment: attachment } end let(:dossier_id) { dossier.to_typed_id } let(:instructeur_id) { instructeur.to_typed_id } let(:input_body) { "Bonjour" } let(:attachment) { nil } context 'success' do let(:attachment) { blob.signed_id } it "should post a message" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierEnvoyerMessage: { message: { body: "Bonjour" }, errors: nil }) end end context 'schema error' do let(:input) do { dossierId: dossier_id, instructeurId: instructeur_id } end it "should fail" do expect(gql_data).to eq(nil) expect(gql_errors).not_to eq(nil) expect(body[:errors].first[:message]).to eq("Variable $input of type DossierEnvoyerMessageInput! was provided invalid value for body (Expected value to not be null)") expect(body[:errors].first.key?(:backtrace)).to be_falsey end end context 'variables error' do let(:variables) { "{" } it "should fail" do expect(gql_data).to eq(nil) expect(gql_errors).not_to eq(nil) expect(body[:errors].first[:message]).to eq("809: unexpected token at '{'") expect(body[:errors].first.key?(:backtrace)).to be_falsey end end context 'validation error' do let(:input_body) { "" } it "should fail" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierEnvoyerMessage: { errors: [{ message: "Votre message ne peut être vide" }], message: nil }) end end context 'upload error' do let(:attachment) { 'fake' } it "should fail" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierEnvoyerMessage: { errors: [{ message: "L’identifiant du fichier téléversé est invalide" }], message: nil }) end end end describe 'dossierPasserEnInstruction' do let(:dossiers) { [dossier2, dossier1, dossier] } let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) } let(:instructeur_id) { instructeur.to_typed_id } let(:disable_notification) { false } let(:query) do "mutation { dossierPasserEnInstruction(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur_id}\", disableNotification: #{disable_notification} }) { dossier { id state motivation } errors { message } } }" end context 'success' do it "should passer en instruction dossier" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierPasserEnInstruction: { dossier: { id: dossier.to_typed_id, state: "en_instruction", motivation: nil }, errors: nil }) perform_enqueued_jobs except: [APIEntreprise::ServiceJob] expect(ActionMailer::Base.deliveries.size).to eq(4) end end context 'validation error' do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } it "should fail" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierPasserEnInstruction: { errors: [{ message: "Le dossier est déjà en instruction" }], dossier: nil }) end end context 'instructeur error' do let(:instructeur_id) { create(:instructeur).to_typed_id } it "should fail" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierPasserEnInstruction: { errors: [{ message: 'L’instructeur n’a pas les droits d’accès à ce dossier' }], dossier: nil }) end end context 'disable notification' do let(:disable_notification) { true } it "should passer en instruction dossier without notification" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierPasserEnInstruction: { dossier: { id: dossier.to_typed_id, state: "en_instruction", motivation: nil }, errors: nil }) perform_enqueued_jobs except: [APIEntreprise::ServiceJob] expect(ActionMailer::Base.deliveries.size).to eq(3) end end end describe 'dossierRepasserEnInstruction' do let(:dossiers) { [dossier2, dossier1, dossier] } let(:dossier) { create(:dossier, :refuse, :with_individual, procedure: procedure) } let(:instructeur_id) { instructeur.to_typed_id } let(:disable_notification) { false } let(:query) do "mutation { dossierRepasserEnInstruction(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur_id}\", disableNotification: #{disable_notification} }) { dossier { id state motivation } errors { message } } }" end context 'success' do it "should repasser en instruction dossier" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierRepasserEnInstruction: { dossier: { id: dossier.to_typed_id, state: "en_instruction", motivation: nil }, errors: nil }) perform_enqueued_jobs except: [APIEntreprise::ServiceJob] expect(ActionMailer::Base.deliveries.size).to eq(4) end end end describe 'dossierRepasserEnConstruction' do let(:dossiers) { [dossier2, dossier1, dossier] } let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:instructeur_id) { instructeur.to_typed_id } let(:disable_notification) { false } let(:query) do "mutation { dossierRepasserEnConstruction(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur_id}\", disableNotification: #{disable_notification} }) { dossier { id state motivation } errors { message } } }" end context 'success' do it "should passer en instruction dossier" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierRepasserEnConstruction: { dossier: { id: dossier.to_typed_id, state: "en_construction", motivation: nil }, errors: nil }) perform_enqueued_jobs except: [APIEntreprise::ServiceJob] expect(ActionMailer::Base.deliveries.size).to eq(3) end end end describe 'dossierClasserSansSuite' do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:query) do "mutation { dossierClasserSansSuite(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", motivation: \"Parce que\", justificatif: \"#{blob.signed_id}\" }) { dossier { id state motivation } errors { message } } }" end context 'success' do it "should classer sans suite dossier" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierClasserSansSuite: { dossier: { id: dossier.to_typed_id, state: "sans_suite", motivation: "Parce que" }, errors: nil }) end end context 'validation error' do let(:dossier) { create(:dossier, :accepte, :with_individual, procedure: procedure) } it "should fail" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierClasserSansSuite: { errors: [{ message: "Le dossier est déjà accepté" }], dossier: nil }) end end end describe 'dossierRefuser' do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:query) do "mutation { dossierRefuser(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", motivation: \"Parce que\", justificatif: \"#{blob.signed_id}\" }) { dossier { id state motivation } errors { message } } }" end context 'success' do it "should refuser dossier" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierRefuser: { dossier: { id: dossier.to_typed_id, state: "refuse", motivation: "Parce que" }, errors: nil }) end end context 'validation error' do let(:dossier) { create(:dossier, :sans_suite, :with_individual, procedure: procedure) } it "should fail" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierRefuser: { errors: [{ message: "Le dossier est déjà classé sans suite" }], dossier: nil }) end end end describe 'dossierAccepter' do let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) } let(:query) do "mutation { dossierAccepter(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", motivation: \"Parce que\", justificatif: \"#{blob.signed_id}\" }) { dossier { id state motivation } errors { message } } }" end context 'success' do it "should accepter dossier" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierAccepter: { dossier: { id: dossier.to_typed_id, state: "accepte", motivation: "Parce que" }, errors: nil }) end end context 'success without motivation' do let(:query) do "mutation { dossierAccepter(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\" }) { dossier { id state motivation } errors { message } } }" end it "should accepter dossier" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierAccepter: { dossier: { id: dossier.to_typed_id, state: "accepte", motivation: nil }, errors: nil }) end end context 'validation error' do let(:dossier) { create(:dossier, :refuse, :with_individual, procedure: procedure) } it "should fail" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierAccepter: { errors: [{ message: "Le dossier est déjà refusé" }], dossier: nil }) end end end describe 'createDirectUpload' do let(:query) do "mutation { createDirectUpload(input: { dossierId: \"#{dossier.to_typed_id}\", filename: \"#{blob_info[:filename]}\", byteSize: #{blob_info[:byte_size]}, checksum: \"#{blob_info[:checksum]}\", contentType: \"#{blob_info[:content_type]}\" }) { directUpload { url headers blobId signedBlobId } } }" end let(:attach_query) do "mutation { dossierEnvoyerMessage(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", body: \"Hello world\", attachment: \"#{direct_upload_blob_id}\" }) { message { body } errors { message } } }" end let(:attach_query_exec) { post :execute, params: { query: attach_query } } let(:attach_query_body) { JSON.parse(attach_query_exec.body, symbolize_names: true) } let(:attach_query_data) { attach_query_body[:data] } let(:direct_upload_data) { gql_data[:createDirectUpload][:directUpload] } let(:direct_upload_blob_id) { direct_upload_data[:signedBlobId] } it "should initiate a direct upload" do expect(gql_errors).to eq(nil) data = gql_data[:createDirectUpload][:directUpload] expect(data[:url]).not_to be_nil expect(data[:headers]).not_to be_nil expect(data[:blobId]).not_to be_nil expect(data[:signedBlobId]).not_to be_nil end it "wrong hash error" do blob = ActiveStorage::Blob.find direct_upload_data[:blobId] blob.service.upload blob.key, StringIO.new('toto') expect(attach_query_data).to eq(dossierEnvoyerMessage: { errors: [{ message: "Le hash du fichier téléversé est invalide" }], message: nil }) end end describe 'dossierChangerGroupeInstructeur' do let(:query) do "mutation { dossierChangerGroupeInstructeur(input: { dossierId: \"#{dossier.to_typed_id}\", groupeInstructeurId: \"#{dossier.groupe_instructeur.to_typed_id}\" }) { errors { message } } }" end it "validation error" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierChangerGroupeInstructeur: { errors: [{ message: "Le dossier est déjà avec le grope instructeur: 'défaut'" }] }) end context "should changer groupe instructeur" do let!(:new_groupe_instructeur) { procedure.groupe_instructeurs.create(label: 'new groupe instructeur') } let(:query) do "mutation { dossierChangerGroupeInstructeur(input: { dossierId: \"#{dossier.to_typed_id}\", groupeInstructeurId: \"#{new_groupe_instructeur.to_typed_id}\" }) { errors { message } } }" end it "change made" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierChangerGroupeInstructeur: { errors: nil }) end end end describe 'dossierArchiver' do let(:query) do "mutation { dossierArchiver(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\" }) { dossier { archived } errors { message } } }" end it "validation error" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierArchiver: { dossier: nil, errors: [{ message: "Un dossier ne peut être archivé qu’une fois le traitement terminé" }] }) end context "should archive dossier" do let(:dossier) { create(:dossier, :sans_suite, :with_individual, procedure: procedure) } it "change made" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierArchiver: { dossier: { archived: true }, errors: nil }) end end end describe 'dossierModifierAnnotation' do let(:procedure) { create(:procedure, :published, :for_individual, :with_service, :with_all_annotations, administrateurs: [admin]) } describe 'text' do let(:query) do "mutation { dossierModifierAnnotationText(input: { dossierId: \"#{dossier.to_typed_id}\", annotationId: \"#{dossier.champs_private.first.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", value: \"hello\" }) { annotation { stringValue } errors { message } } }" end context "success" do it 'should be a success' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierModifierAnnotationText: { annotation: { stringValue: 'hello' }, errors: nil }) end end end describe 'checkbox' do let(:value) { 'true' } let(:query) do "mutation { dossierModifierAnnotationCheckbox(input: { dossierId: \"#{dossier.to_typed_id}\", annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'checkbox' }.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", value: #{value} }) { annotation { stringValue } errors { message } } }" end context "success when true" do it 'should be a success' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierModifierAnnotationCheckbox: { annotation: { stringValue: 'true' }, errors: nil }) end end context "success when false" do let(:value) { 'false' } it 'should be a success' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierModifierAnnotationCheckbox: { annotation: { stringValue: 'false' }, errors: nil }) end end end describe 'yes_no' do let(:value) { 'true' } let(:query) do "mutation { dossierModifierAnnotationCheckbox(input: { dossierId: \"#{dossier.to_typed_id}\", annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'yes_no' }.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", value: #{value} }) { annotation { stringValue } errors { message } } }" end context "success when true" do it 'should be a success' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierModifierAnnotationCheckbox: { annotation: { stringValue: 'true' }, errors: nil }) end end context "success when false" do let(:value) { 'false' } it 'should be a success' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierModifierAnnotationCheckbox: { annotation: { stringValue: 'false' }, errors: nil }) end end end describe 'date' do let(:query) do "mutation { dossierModifierAnnotationDate(input: { dossierId: \"#{dossier.to_typed_id}\", annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'date' }.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", value: \"#{1.day.from_now.to_date.iso8601}\" }) { annotation { stringValue } errors { message } } }" end context "success" do it 'should be a success' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierModifierAnnotationDate: { annotation: { stringValue: dossier.reload.champs_private.find { |c| c.type_champ == 'date' }.to_s }, errors: nil }) end end end describe 'datetime' do let(:query) do "mutation { dossierModifierAnnotationDatetime(input: { dossierId: \"#{dossier.to_typed_id}\", annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'datetime' }.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", value: \"#{1.day.from_now.iso8601}\" }) { annotation { stringValue } errors { message } } }" end context "success" do it 'should be a success' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierModifierAnnotationDatetime: { annotation: { stringValue: dossier.reload.champs_private.find { |c| c.type_champ == 'datetime' }.to_s }, errors: nil }) end end end describe 'integer_number' do let(:query) do "mutation { dossierModifierAnnotationIntegerNumber(input: { dossierId: \"#{dossier.to_typed_id}\", annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'integer_number' }.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", value: 42 }) { annotation { stringValue } errors { message } } }" end context "success" do it 'should be a success' do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossierModifierAnnotationIntegerNumber: { annotation: { stringValue: '42' }, errors: nil }) end end end end end end context "when not authenticated" do it "should return error" do expect(gql_data).to eq(nil) expect(gql_errors).not_to eq(nil) end describe "dossier" do let(:query) { "{ dossier(number: #{dossier.id}) { id number usager { email } } }" } it "should return error" do expect(gql_data).to eq(nil) expect(gql_errors).not_to eq(nil) end end describe "mutation" do let(:query) do "mutation { dossierEnvoyerMessage(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", body: \"Bonjour\" }) { message { body } } }" end it "should return error" do expect(gql_data[:dossierEnvoyerMessage]).to eq(nil) expect(gql_errors).not_to eq(nil) end end end end