From 3a15587aeaff5c07d9175b0bdd93adf67a99339b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Ce=C4=BEuch?= Date: Mon, 18 Sep 2023 08:59:11 +0200 Subject: [PATCH] ND-231 Add UPVS submission tests (#600) * start tests * test progress * tests wip * add upvs submission spec * rm upvs_submissions factory * add datahub db to tests * fix upvs submission model spec * add test env value for sk api url to GH workflow --- .github/workflows/slovensko_digital_ci.yml | 7 +- Gemfile | 2 + Gemfile.lock | 5 + .../upvs/submissions_controller.rb | 34 +--- app/models/upvs/submission.rb | 45 ++++- config/database.yml | 1 - db/datahub_structure.sql | 169 ++++++++++++++++++ db/structure.sql | 52 ++++++ spec/models/upvs/submission_spec.rb | 38 ++++ .../requests/upvs/submissions_request_spec.rb | 136 ++++++++++++++ 10 files changed, 450 insertions(+), 39 deletions(-) create mode 100644 db/datahub_structure.sql create mode 100644 spec/models/upvs/submission_spec.rb create mode 100644 spec/requests/upvs/submissions_request_spec.rb diff --git a/.github/workflows/slovensko_digital_ci.yml b/.github/workflows/slovensko_digital_ci.yml index 20383352..024d406c 100644 --- a/.github/workflows/slovensko_digital_ci.yml +++ b/.github/workflows/slovensko_digital_ci.yml @@ -1,11 +1,11 @@ name: Slovensko.Digital CI -on: - push: +on: + push: branches: master pull_request: branches: '**' - + jobs: test: runs-on: ubuntu-latest @@ -13,6 +13,7 @@ jobs: env: PGHOST: localhost RAILS_ENV: test + SLOVENSKO_SK_API_URL: https://test.slovensko.digital services: postgres: diff --git a/Gemfile b/Gemfile index 81bcb7b6..661419ff 100644 --- a/Gemfile +++ b/Gemfile @@ -108,3 +108,5 @@ end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'hirb' + +gem "rails-controller-testing", "~> 1.0" diff --git a/Gemfile.lock b/Gemfile.lock index 6beb8d54..68cbbd8b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -317,6 +317,10 @@ GEM bundler (>= 1.15.0) railties (= 6.1.7.3) sprockets-rails (>= 2.0.0) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) @@ -490,6 +494,7 @@ DEPENDENCIES que que-web rails (~> 6.1.7.3) + rails-controller-testing (~> 1.0) rails-i18n recaptcha rollbar diff --git a/app/controllers/upvs/submissions_controller.rb b/app/controllers/upvs/submissions_controller.rb index 4b89d855..af6b9232 100644 --- a/app/controllers/upvs/submissions_controller.rb +++ b/app/controllers/upvs/submissions_controller.rb @@ -35,17 +35,11 @@ def switch_account_callback def submit return switch_account_callback unless eid_token&.valid? # If token expires meanwhile - if @upvs_submission.valid? - response = submit_to_sk_api - - if successful_sk_api_submission?(response) - if @upvs_submission.callback_url.present? - redirect_to @upvs_submission.callback_url - else - redirect_to action: :finish - end + if @upvs_submission.submit(eid_token) + if @upvs_submission.callback_url.present? + redirect_to @upvs_submission.callback_url else - raise Upvs::Submission::SkApiError.new + redirect_to action: :finish end else render :new, status: :unprocessable_entity @@ -53,7 +47,6 @@ def submit end def finish - @upvs_submission.update(expires_at: Time.zone.now) end def submission_error @@ -76,25 +69,6 @@ def set_metadata @metadata.og.title = params[:title] || 'Návody.Digital: Podanie' # TODO end - def submit_to_sk_api(client: Faraday) - begin - headers = { "Content-Type": "application/json" } - data = { message: UpvsSubmissions::SktalkMessageBuilder.new.build_sktalk_message(@upvs_submission, eid_token) }.to_json - url = "#{ENV.fetch('SLOVENSKO_SK_API_URL')}/api/sktalk/receive_and_save_to_outbox?token=#{eid_token&.api_token}" - - client.post(url, data, headers) - rescue Exception - raise Upvs::Submission::SkApiError.new - end - end - - def successful_sk_api_submission?(response) - json_body = JSON.parse(response.body) - - return true if (response.status == 200 && json_body["receive_result"] == 0 && json_body["save_to_outbox_result"] == 0) - false - end - def build_upvs_submission @upvs_submission = current_user.build_upvs_submission( submission_params, diff --git a/app/models/upvs/submission.rb b/app/models/upvs/submission.rb index fb437237..eadf849e 100644 --- a/app/models/upvs/submission.rb +++ b/app/models/upvs/submission.rb @@ -31,10 +31,10 @@ class Upvs::Submission < ApplicationRecord before_create { self.uuid = SecureRandom.uuid } # TODO ensure unique in loop before_create { set_new_expiration_time } - validates_presence_of :posp_id, :posp_version, :message_type, :recipient_uri, :message_subject, :form - validate :recipient_uri_allowed?, if: -> { Rails.env.production? } - validate :egov_application_allowed?, if: -> { Rails.env.production? } - validate :valid_xml_form? + validates_presence_of :posp_id, :posp_version, :message_type, :recipient_uri, :message_subject, :form, on: :create + validate :recipient_uri_allowed?, if: -> { Rails.env.production? }, on: :create + validate :egov_application_allowed?, if: -> { Rails.env.production? }, on: :create + validate :valid_xml_form?, on: :create scope :expired, -> { where('expires_at < ?', Time.zone.now) } @@ -68,8 +68,43 @@ def to_param uuid end + def submit(eid_token, client: Faraday, url: "#{ENV.fetch('SLOVENSKO_SK_API_URL')}/api/sktalk/receive_and_save_to_outbox?token=#{eid_token&.api_token}") + return false unless valid? + + response = submit_to_sk_api(client, url, eid_token) + + if successful_sk_api_submission?(response) + update(expires_at: Time.zone.now) + return true + else + raise Upvs::Submission::SkApiError.new + end + end + private + def validate + false unless valid? + end + + def submit_to_sk_api(client, url, eid_token) + begin + headers = { "Content-Type": "application/json" } + data = { message: UpvsSubmissions::SktalkMessageBuilder.new.build_sktalk_message(self, eid_token) }.to_json + + client.post(url, data, headers) + rescue Exception + raise Upvs::Submission::SkApiError.new + end + end + + def successful_sk_api_submission?(response) + json_body = JSON.parse(response.body) + + return true if (response.status == 200 && json_body["receive_result"] == 0 && json_body["save_to_outbox_result"] == 0) + false + end + def set_new_expiration_time self.expires_at = Time.zone.now + 20.minutes end @@ -97,7 +132,7 @@ def recipient_uri_allowed? end def valid_xml_form? - unless Nokogiri::XML(@form).errors.empty? + unless Nokogiri::XML(form).errors.empty? errors.add(:form, "Nevalidný XML formulár") end end diff --git a/config/database.yml b/config/database.yml index c8d310b0..f97b7d50 100644 --- a/config/database.yml +++ b/config/database.yml @@ -21,7 +21,6 @@ test: datahub: <<: *default database: datahub_test - replica: true staging: primary: diff --git a/db/datahub_structure.sql b/db/datahub_structure.sql new file mode 100644 index 00000000..a4ab03df --- /dev/null +++ b/db/datahub_structure.sql @@ -0,0 +1,169 @@ +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: upvs; Type: SCHEMA; Schema: -; Owner: - +-- + +CREATE SCHEMA upvs; + + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: public_authority_edesks; Type: TABLE; Schema: upvs; Owner: - +-- + +CREATE TABLE upvs.public_authority_edesks ( + id integer NOT NULL, + cin bigint NOT NULL, + uri character varying NOT NULL, + name character varying NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: public_authority_edesks_id_seq; Type: SEQUENCE; Schema: upvs; Owner: - +-- + +CREATE SEQUENCE upvs.public_authority_edesks_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: public_authority_edesks_id_seq; Type: SEQUENCE OWNED BY; Schema: upvs; Owner: - +-- + +ALTER SEQUENCE upvs.public_authority_edesks_id_seq OWNED BY upvs.public_authority_edesks.id; + + +-- +-- Name: services_with_forms; Type: TABLE; Schema: upvs; Owner: - +-- + +CREATE TABLE upvs.services_with_forms ( + id integer NOT NULL, + instance_id integer NOT NULL, + external_code character varying, + meta_is_code character varying, + name character varying, + type character varying, + institution_uri character varying NOT NULL, + institution_name character varying, + valid_from timestamp without time zone, + valid_to timestamp without time zone, + url character varying, + info_url character varying, + schema_url character varying, + changed_at timestamp without time zone, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: services_with_forms_id_seq; Type: SEQUENCE; Schema: upvs; Owner: - +-- + +CREATE SEQUENCE upvs.services_with_forms_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: services_with_forms_id_seq; Type: SEQUENCE OWNED BY; Schema: upvs; Owner: - +-- + +ALTER SEQUENCE upvs.services_with_forms_id_seq OWNED BY upvs.services_with_forms.id; + +-- +-- Name: public_authority_edesks id; Type: DEFAULT; Schema: upvs; Owner: - +-- + +ALTER TABLE ONLY upvs.public_authority_edesks ALTER COLUMN id SET DEFAULT nextval('upvs.public_authority_edesks_id_seq'::regclass); + + +-- +-- Name: services_with_forms id; Type: DEFAULT; Schema: upvs; Owner: - +-- + +ALTER TABLE ONLY upvs.services_with_forms ALTER COLUMN id SET DEFAULT nextval('upvs.services_with_forms_id_seq'::regclass); + + +-- +-- Name: public_authority_edesks public_authority_edesks_pkey; Type: CONSTRAINT; Schema: upvs; Owner: - +-- + +ALTER TABLE ONLY upvs.public_authority_edesks + ADD CONSTRAINT public_authority_edesks_pkey PRIMARY KEY (id); + + +-- +-- Name: services_with_forms services_with_forms_pkey; Type: CONSTRAINT; Schema: upvs; Owner: - +-- + +ALTER TABLE ONLY upvs.services_with_forms + ADD CONSTRAINT services_with_forms_pkey PRIMARY KEY (id); + + +-- +-- Name: index_upvs.public_authority_edesks_on_cin; Type: INDEX; Schema: upvs; Owner: - +-- + +CREATE INDEX "index_upvs.public_authority_edesks_on_cin" ON upvs.public_authority_edesks USING btree (cin); + + +-- +-- Name: index_upvs.public_authority_edesks_on_uri; Type: INDEX; Schema: upvs; Owner: - +-- + +CREATE UNIQUE INDEX "index_upvs.public_authority_edesks_on_uri" ON upvs.public_authority_edesks USING btree (uri); + + +-- +-- Name: index_upvs.services_with_forms_on_instance_id; Type: INDEX; Schema: upvs; Owner: - +-- + +CREATE UNIQUE INDEX "index_upvs.services_with_forms_on_instance_id" ON upvs.services_with_forms USING btree (instance_id); + + +-- +-- Name: index_upvs.services_with_forms_on_institution_uri; Type: INDEX; Schema: upvs; Owner: - +-- + +CREATE INDEX "index_upvs.services_with_forms_on_institution_uri" ON upvs.services_with_forms USING btree (institution_uri); + + +-- +-- Name: index_upvs.services_with_forms_on_schema_url; Type: INDEX; Schema: upvs; Owner: - +-- + +CREATE INDEX "index_upvs.services_with_forms_on_schema_url" ON upvs.services_with_forms USING btree (schema_url); + + +-- +-- PostgreSQL database dump complete +-- + +SET search_path TO "$user", public; diff --git a/db/structure.sql b/db/structure.sql index 8d8fa562..22e99246 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1325,6 +1325,41 @@ CREATE SEQUENCE upvs.form_template_related_documents_id_seq ALTER SEQUENCE upvs.form_template_related_documents_id_seq OWNED BY upvs.form_template_related_documents.id; +-- +-- Name: form_template_related_documents_temp; Type: TABLE; Schema: upvs; Owner: - +-- + +CREATE TABLE upvs.form_template_related_documents_temp ( + id bigint NOT NULL, + posp_id character varying NOT NULL, + posp_version character varying NOT NULL, + message_type character varying NOT NULL, + xsd_schema text, + xslt_transformation text, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: form_template_related_documents_temp_id_seq; Type: SEQUENCE; Schema: upvs; Owner: - +-- + +CREATE SEQUENCE upvs.form_template_related_documents_temp_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: form_template_related_documents_temp_id_seq; Type: SEQUENCE OWNED BY; Schema: upvs; Owner: - +-- + +ALTER SEQUENCE upvs.form_template_related_documents_temp_id_seq OWNED BY upvs.form_template_related_documents_temp.id; + + -- -- Name: submissions; Type: TABLE; Schema: upvs; Owner: - -- @@ -1581,6 +1616,13 @@ ALTER TABLE ONLY upvs.egov_application_allow_rules ALTER COLUMN id SET DEFAULT n ALTER TABLE ONLY upvs.form_template_related_documents ALTER COLUMN id SET DEFAULT nextval('upvs.form_template_related_documents_id_seq'::regclass); +-- +-- Name: form_template_related_documents_temp id; Type: DEFAULT; Schema: upvs; Owner: - +-- + +ALTER TABLE ONLY upvs.form_template_related_documents_temp ALTER COLUMN id SET DEFAULT nextval('upvs.form_template_related_documents_temp_id_seq'::regclass); + + -- -- Name: submissions id; Type: DEFAULT; Schema: upvs; Owner: - -- @@ -1860,6 +1902,14 @@ ALTER TABLE ONLY upvs.form_template_related_documents ADD CONSTRAINT form_template_related_documents_pkey PRIMARY KEY (id); +-- +-- Name: form_template_related_documents_temp form_template_related_documents_temp_pkey; Type: CONSTRAINT; Schema: upvs; Owner: - +-- + +ALTER TABLE ONLY upvs.form_template_related_documents_temp + ADD CONSTRAINT form_template_related_documents_temp_pkey PRIMARY KEY (id); + + -- -- Name: submissions submissions_pkey; Type: CONSTRAINT; Schema: upvs; Owner: - -- @@ -1867,6 +1917,8 @@ ALTER TABLE ONLY upvs.form_template_related_documents ALTER TABLE ONLY upvs.submissions ADD CONSTRAINT submissions_pkey PRIMARY KEY (id); + +-- -- Name: index_active_storage_attachments_on_blob_id; Type: INDEX; Schema: public; Owner: - -- diff --git a/spec/models/upvs/submission_spec.rb b/spec/models/upvs/submission_spec.rb new file mode 100644 index 00000000..6d518fb2 --- /dev/null +++ b/spec/models/upvs/submission_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +RSpec.describe Upvs::Submission, type: :model do + context 'with valid attributes' do + let(:valid_attributes) do + { + posp_id: 'posp_id', + posp_version: 'posp_version', + message_type: 'message_type', + recipient_uri: 'recipient_uri', + message_subject: 'message_subject', + form: '
', + title: 'title' + } + end + + it 'is valid' do + submission = described_class.new(valid_attributes) + expect(submission).to be_valid + expect(submission.save).to be_truthy + expect(Upvs::Submission.last.expires_at).to be <= 20.minutes.from_now + expect(Upvs::Submission.last.expires_at).to be > 19.minutes.from_now + end + + context 'submit' do + let(:submission) { described_class.new(valid_attributes) } + + it 'is valid' do + response_dbl = instance_double('Faraday::Response') + allow(Faraday).to receive(:post) + .and_return(OpenStruct.new(body: { "receive_result" => 0, "save_to_outbox_result" => 0 }.to_json, status: 200)) + + allow_any_instance_of(EidToken).to receive(:subject_sub).and_return("subject_sub") + expect(submission.submit(EidToken.new("eid_token", config: nil), client: Faraday, url: 'https://testing.stub.com/')).to be_truthy + end + end + end +end diff --git a/spec/requests/upvs/submissions_request_spec.rb b/spec/requests/upvs/submissions_request_spec.rb new file mode 100644 index 00000000..cf958f1e --- /dev/null +++ b/spec/requests/upvs/submissions_request_spec.rb @@ -0,0 +1,136 @@ +require 'rails_helper' + +RSpec.describe "Upvs::Submissions", type: :request do + let(:valid_attributes) do + { + posp_id: 'posp_id', + posp_version: 'posp_version', + message_type: 'message_type', + recipient_uri: 'foo://bar', + message_subject: 'message_subject', + form: '
9
', + title: 'title' + } + end + + let(:invalid_attributes) do + { + posp_id: 'posp_id', + posp_version: 'posp_version', + message_type: 'message_type', + recipient_uri: '', + message_subject: 'message_subject', + form: '
', + title: 'title' + } + end + + let(:form_template) do + { + posp_id: 'posp_id', + posp_version: 'posp_version', + message_type: 'message_type', + xsd_schema: ' + ', + xslt_transformation: ' + + + + + + + + + + ' + } + end + + before do + Upvs::FormTemplateRelatedDocument.create!(form_template) + end + + describe "POST create" do + context "with valid params" do + it "creates a new Submission" do + expect { + post upvs_submissions_path, params: { upvs_submission: valid_attributes } + }.to change(Upvs::Submission, :count).by(1) + end + + it "redirects to the created submission" do + post upvs_submissions_path, params: { upvs_submission: valid_attributes } + expect(response).to redirect_to(upvs_submission_path(Upvs::Submission.last)) + end + + it "creates a new Submission that expires in 20 minutes" do + post upvs_submissions_path, params: { upvs_submission: valid_attributes } + expect(Upvs::Submission.last.expires_at).to be <= 20.minutes.from_now + expect(Upvs::Submission.last.expires_at).to be > 19.minutes.from_now + end + end + + context 'with empty recipient_uri' do + it 'raises ActiveRecord::RecordInvalid' do + expect { + post upvs_submissions_path, params: { upvs_submission: invalid_attributes } + }.to raise_error(ActiveRecord::RecordInvalid) + end + end + + context 'with invalid xml form' do + it 'raises ActiveRecord::RecordInvalid' do + expect { + post upvs_submissions_path, params: { upvs_submission: invalid_attributes } + }.to raise_error(ActiveRecord::RecordInvalid) + end + end + end + + describe "GET show" do + context "with existing submission and no token" do + it "renders the show template" do + post upvs_submissions_path, params: { upvs_submission: valid_attributes } + get upvs_submission_path(Upvs::Submission.last) + expect(response).to render_template(:show) + end + end + + context "with existing submission and valid token" do + it "renders the show template" do + post upvs_submissions_path, params: { upvs_submission: valid_attributes } + get upvs_submission_path(Upvs::Submission.last), params: { token: "sadfasd" } + expect(response).to render_template(:show) + end + end + end + + describe "POST submit" do + + context "with valid params" do + it "redirects to the callback url" do + # stub valid eid_token + allow_any_instance_of(EidToken).to receive(:valid?).and_return(true) + allow_any_instance_of(EidToken).to receive(:api_token).and_return("eid_token") + allow_any_instance_of(EidToken).to receive(:subject_sub).and_return("subject_sub") + allow_any_instance_of(EidToken).to receive(:subject_name).and_return("Test User") + # stub request to sk api + allow_any_instance_of(Faraday::Connection).to receive(:post).and_return(OpenStruct.new(body: { "receive_result" => 0, "save_to_outbox_result" => 0 }.to_json, status: 200)) + + post upvs_submissions_path, params: { upvs_submission: valid_attributes } + get upvs_submission_path(Upvs::Submission.last), params: { token: "token" } + expect(response.body).to include("Prihlásiť sa pomocou slovensko.sk") + + get login_callback_upvs_submissions_path(Upvs::Submission.last), params: { token: "eid_token" } + expect(response).to redirect_to upvs_submission_path(Upvs::Submission.last, token: "token") + expect(session[:eid_encoded_token]).to eq("eid_token") + + get upvs_submission_path(Upvs::Submission.last), params: { token: "token" } + expect(response.body).to include("Odoslať ako Test User") + + post upvs_submission_submit_path(Upvs::Submission.last), params: { :token => "asdfasdf", eid_token: "asdfasdf" } + expect(response.location).to redirect_to upvs_submission_finish_path(Upvs::Submission.last) + end + end + end +end