diff --git a/app/services/documentation_generator.rb b/app/services/documentation_generator.rb index 4e0a7011c..7708fb48e 100644 --- a/app/services/documentation_generator.rb +++ b/app/services/documentation_generator.rb @@ -9,9 +9,7 @@ class DocumentationGenerator include Validations::SoftValidations include Validations::Sales::SoftValidations - def describe_hard_validations(client, all_validation_methods, all_helper_methods, log_type) - form = FormHandler.instance.forms["current_#{log_type}"] - + def describe_hard_validations(client, form, all_validation_methods, all_helper_methods, log_type) all_validation_methods.each do |meth| if LogValidation.where(validation_name: meth.to_s, bulk_upload_specific: false, log_type:, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}").exists? Rails.logger.info("Validation #{meth} already exists for #{form.start_date.year}") @@ -69,7 +67,7 @@ class DocumentationGenerator end end - def describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type) + def describe_soft_validations(client, form, all_validation_methods, all_helper_methods, log_type) validation_descriptions = {} all_validation_methods[0..5].each do |meth| validation_source = method(meth).source @@ -87,16 +85,11 @@ class DocumentationGenerator validation_descriptions[meth.to_s] = result end - current_form = FormHandler.instance.forms["current_#{log_type}"] - previous_form = FormHandler.instance.forms["previous_#{log_type}"] - - [current_form, previous_form].each do |form| - interruption_screen_pages = form.pages.select { |page| page.questions.first.type == "interruption_screen" } - interruption_screen_pages_grouped_by_question = interruption_screen_pages.group_by { |page| page.questions.first.id } - interruption_screen_pages_grouped_by_question.each do |_question_id, pages| - pages.map do |page| - save_soft_validation(form, page, validation_descriptions, log_type) - end + interruption_screen_pages = form.pages.select { |page| page.questions.first.type == "interruption_screen" } + interruption_screen_pages_grouped_by_question = interruption_screen_pages.group_by { |page| page.questions.first.id } + interruption_screen_pages_grouped_by_question.each do |_question_id, pages| + pages.map do |page| + save_soft_validation(form, page, validation_descriptions, log_type) end end end diff --git a/lib/tasks/generate_lettings_documentation.rake b/lib/tasks/generate_lettings_documentation.rake index e87fd8dca..1f43efbe9 100644 --- a/lib/tasks/generate_lettings_documentation.rake +++ b/lib/tasks/generate_lettings_documentation.rake @@ -1,6 +1,12 @@ namespace :generate_lettings_documentation do desc "Generate documentation for hard lettings validations" - task describe_lettings_validations: :environment do + task :describe_lettings_validations, %i[year] => :environment do |_task, args| + form_year = args[:year]&.to_i + raise "Usage: rake generate_lettings_documentation:describe_lettings_validations['year']" if form_year.blank? + + form = FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(form_year, "lettings")] + raise "No form found for given year" if form.blank? + client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"]) include Validations::SetupValidations include Validations::HouseholdValidations @@ -19,39 +25,52 @@ namespace :generate_lettings_documentation do Validations::LocalAuthorityValidations].map { |x| x.instance_methods + x.private_instance_methods }.flatten all_helper_methods = all_methods - all_validation_methods - DocumentationGenerator.new.describe_hard_validations(client, all_validation_methods, all_helper_methods, "lettings") + DocumentationGenerator.new.describe_hard_validations(client, form, all_validation_methods, all_helper_methods, "lettings") end desc "Generate documentation for soft lettings validations" - task describe_soft_lettings_validations: :environment do + task :describe_soft_lettings_validations, %i[year] => :environment do |_task, args| include Validations::SoftValidations + form_year = args[:year]&.to_i + raise "Usage: rake generate_lettings_documentation:describe_soft_lettings_validations['year']" if form_year.blank? + + form = FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(form_year, "lettings")] + raise "No form found for given year" if form.blank? client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"]) all_helper_methods = Validations::SoftValidations.private_instance_methods all_validation_methods = Validations::SoftValidations.instance_methods - DocumentationGenerator.new.describe_soft_validations(client, all_validation_methods, all_helper_methods, "lettings") + DocumentationGenerator.new.describe_soft_validations(client, form, all_validation_methods, all_helper_methods, "lettings") end desc "Generate documentation for hard bu lettings validations" - task describe_bu_lettings_validations: :environment do + task :describe_bu_lettings_validations, %i[year] => :environment do |_task, args| + form_year = args[:year]&.to_i + raise "Usage: rake generate_lettings_documentation:describe_bu_lettings_validations['year']" if form_year.blank? + + form = FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(form_year, "lettings")] + raise "No form found for given year" if form.blank? + + row_parser_class = "BulkUpload::Lettings::Year#{form_year}::RowParser".constantize client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"]) - [[FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(2023, "lettings")], BulkUpload::Lettings::Year2023::RowParser], - [FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(2024, "lettings")], BulkUpload::Lettings::Year2024::RowParser]].each do |form, row_parser_class| - all_validation_methods = row_parser_class.private_instance_methods.select { |method| method.starts_with?("validate_") } + all_validation_methods = row_parser_class.private_instance_methods.select { |method| method.starts_with?("validate_") } - all_helper_methods = row_parser_class.private_instance_methods(false) + row_parser_class.instance_methods(false) - all_validation_methods + all_helper_methods = row_parser_class.private_instance_methods(false) + row_parser_class.instance_methods(false) - all_validation_methods - field_mapping_for_errors = row_parser_class.new.send("field_mapping_for_errors") - DocumentationGenerator.new.describe_bu_validations(client, form, row_parser_class, all_validation_methods, all_helper_methods, field_mapping_for_errors, "lettings") - end + field_mapping_for_errors = row_parser_class.new.send("field_mapping_for_errors") + DocumentationGenerator.new.describe_bu_validations(client, form, row_parser_class, all_validation_methods, all_helper_methods, field_mapping_for_errors, "lettings") end desc "Generate documentation for lettings numeric validations" - task add_numeric_lettings_validations: :environment do - form = FormHandler.instance.forms["current_lettings"] + task :add_numeric_lettings_validations, %i[year] => :environment do |_task, args| + form_year = args[:year]&.to_i + raise "Usage: rake generate_lettings_documentation:add_numeric_lettings_validations['year']" if form_year.blank? + + form = FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(form_year, "lettings")] + raise "No form found for given year" if form.blank? form.numeric_questions.each do |question| next unless question.min || question.max diff --git a/spec/lib/tasks/generate_lettings_documentation_spec.rb b/spec/lib/tasks/generate_lettings_documentation_spec.rb index eca123b4e..6b7e8c2f6 100644 --- a/spec/lib/tasks/generate_lettings_documentation_spec.rb +++ b/spec/lib/tasks/generate_lettings_documentation_spec.rb @@ -6,14 +6,24 @@ RSpec.describe "generate_lettings_documentation" do subject(:task) { Rake::Task["generate_lettings_documentation:add_numeric_lettings_validations"] } before do + Timecop.freeze(Time.zone.local(2025, 1, 1)) + Singleton.__init__(FormHandler) + allow(FormHandler.instance).to receive(:forms).and_return({ "current_lettings" => FormHandler.instance.forms["current_lettings"], "previous_lettings" => "2023_form", "next_lettings" => "2025_form" }) Rake.application.rake_require("tasks/generate_lettings_documentation") Rake::Task.define_task(:environment) task.reenable end + after do + Timecop.return + Singleton.__init__(FormHandler) + end + context "when the rake task is run" do it "creates new validation documentation records" do - expect { task.invoke }.to change(LogValidation, :count) + allow(FormHandler.instance.forms).to receive(:[]).with("current_lettings").and_return(FormHandler.instance.forms["current_lettings"]) + + expect { task.invoke(2024) }.to change(LogValidation, :count) expect(LogValidation.where(validation_name: "minimum").count).to be_positive expect(LogValidation.where(validation_name: "range").count).to be_positive any_min_validation = LogValidation.where(validation_name: "minimum").first @@ -30,8 +40,194 @@ RSpec.describe "generate_lettings_documentation" do end it "skips if the validation already exists in the database" do - task.invoke - expect { task.invoke }.not_to change(LogValidation, :count) + task.invoke(2024) + expect { task.invoke(2024) }.not_to change(LogValidation, :count) + end + + context "with no year given" do + it "raises an error" do + expect { task.invoke(nil) }.to raise_error(RuntimeError, "Usage: rake generate_lettings_documentation:add_numeric_lettings_validations['year']") + end + end + + context "with an invalid year given" do + it "raises an error" do + expect { task.invoke("abc") }.to raise_error(RuntimeError, "No form found for given year") + end + end + + context "with a year for non existing form" do + it "raises an error" do + expect { task.invoke("2022") }.to raise_error(RuntimeError, "No form found for given year") + end + end + end + end + + describe ":describe_lettings_validations", type: :task do + subject(:task) { Rake::Task["generate_lettings_documentation:describe_lettings_validations"] } + + let(:documentation_generator) { instance_double(DocumentationGenerator, describe_bu_validations: nil) } + let(:client) { instance_double(OpenAI::Client) } + + before do + allow(OpenAI::Client).to receive(:new).and_return(client) + allow(DocumentationGenerator).to receive(:new).and_return(documentation_generator) + Timecop.freeze(Time.zone.local(2025, 1, 1)) + Singleton.__init__(FormHandler) + allow(FormHandler.instance).to receive(:forms).and_return({ "current_lettings" => "2024_form", "previous_lettings" => "2023_form", "next_lettings" => "2025_form" }) + Rake.application.rake_require("tasks/generate_lettings_documentation") + Rake::Task.define_task(:environment) + task.reenable + end + + after do + Timecop.return + Singleton.__init__(FormHandler) + end + + context "with a year given" do + it "gets the correct form for next year" do + allow(FormHandler.instance.forms).to receive(:[]).with("next_lettings").and_return("2025_form") + expect(documentation_generator).to receive(:describe_hard_validations).with(client, "2025_form", anything, anything, "lettings") + + task.invoke("2025") + end + + it "gets the correct form for current year" do + allow(FormHandler.instance.forms).to receive(:[]).with("current_lettings").and_return("2024_form") + expect(documentation_generator).to receive(:describe_hard_validations).with(client, "2024_form", anything, anything, "lettings") + task.invoke("2024") + end + end + + context "with no year given" do + it "raises an error" do + expect { task.invoke(nil) }.to raise_error(RuntimeError, "Usage: rake generate_lettings_documentation:describe_lettings_validations['year']") + end + end + + context "with an invalid year given" do + it "raises an error" do + expect { task.invoke("abc") }.to raise_error(RuntimeError, "No form found for given year") + end + end + + context "with a year for non existing form" do + it "raises an error" do + expect { task.invoke("2022") }.to raise_error(RuntimeError, "No form found for given year") + end + end + end + + describe ":describe_bu_lettings_validations", type: :task do + subject(:task) { Rake::Task["generate_lettings_documentation:describe_bu_lettings_validations"] } + + let(:documentation_generator) { instance_double(DocumentationGenerator, describe_bu_validations: nil) } + let(:client) { instance_double(OpenAI::Client) } + + before do + allow(OpenAI::Client).to receive(:new).and_return(client) + allow(DocumentationGenerator).to receive(:new).and_return(documentation_generator) + Timecop.freeze(Time.zone.local(2025, 1, 1)) + Singleton.__init__(FormHandler) + allow(FormHandler.instance).to receive(:forms).and_return({ "current_lettings" => "2024_form", "previous_lettings" => "2023_form", "next_lettings" => "2025_form" }) + Rake.application.rake_require("tasks/generate_lettings_documentation") + Rake::Task.define_task(:environment) + task.reenable + end + + after do + Timecop.return + Singleton.__init__(FormHandler) + end + + context "with a year given" do + it "gets the correct form for next year" do + allow(FormHandler.instance.forms).to receive(:[]).with("next_lettings").and_return("2025_form") + expect(documentation_generator).to receive(:describe_bu_validations).with(client, "2025_form", anything, anything, anything, anything, "lettings") + + task.invoke("2025") + end + + it "gets the correct form for current year" do + allow(FormHandler.instance.forms).to receive(:[]).with("current_lettings").and_return("2024_form") + expect(documentation_generator).to receive(:describe_bu_validations).with(client, "2024_form", anything, anything, anything, anything, "lettings") + task.invoke("2024") + end + end + + context "with no year given" do + it "raises an error" do + expect { task.invoke(nil) }.to raise_error(RuntimeError, "Usage: rake generate_lettings_documentation:describe_bu_lettings_validations['year']") + end + end + + context "with an invalid year given" do + it "raises an error" do + expect { task.invoke("abc") }.to raise_error(RuntimeError, "No form found for given year") + end + end + + context "with a year for non existing form" do + it "raises an error" do + expect { task.invoke("2022") }.to raise_error(RuntimeError, "No form found for given year") + end + end + end + + describe ":describe_soft_lettings_validations", type: :task do + subject(:task) { Rake::Task["generate_lettings_documentation:describe_soft_lettings_validations"] } + + let(:documentation_generator) { instance_double(DocumentationGenerator, describe_bu_validations: nil) } + let(:client) { instance_double(OpenAI::Client) } + + before do + allow(OpenAI::Client).to receive(:new).and_return(client) + allow(DocumentationGenerator).to receive(:new).and_return(documentation_generator) + Timecop.freeze(Time.zone.local(2025, 1, 1)) + Singleton.__init__(FormHandler) + allow(FormHandler.instance).to receive(:forms).and_return({ "current_lettings" => "2024_form", "previous_lettings" => "2023_form", "next_lettings" => "2025_form" }) + Rake.application.rake_require("tasks/generate_lettings_documentation") + Rake::Task.define_task(:environment) + task.reenable + end + + after do + Timecop.return + Singleton.__init__(FormHandler) + end + + context "with a year given" do + it "gets the correct form for next year" do + allow(FormHandler.instance.forms).to receive(:[]).with("next_lettings").and_return("2025_form") + expect(documentation_generator).to receive(:describe_soft_validations).with(client, "2025_form", anything, anything, "lettings") + + task.invoke("2025") + end + + it "gets the correct form for current year" do + allow(FormHandler.instance.forms).to receive(:[]).with("current_lettings").and_return("2024_form") + expect(documentation_generator).to receive(:describe_soft_validations).with(client, "2024_form", anything, anything, "lettings") + task.invoke("2024") + end + end + + context "with no year given" do + it "raises an error" do + expect { task.invoke(nil) }.to raise_error(RuntimeError, "Usage: rake generate_lettings_documentation:describe_soft_lettings_validations['year']") + end + end + + context "with an invalid year given" do + it "raises an error" do + expect { task.invoke("abc") }.to raise_error(RuntimeError, "No form found for given year") + end + end + + context "with a year for non existing form" do + it "raises an error" do + expect { task.invoke("2022") }.to raise_error(RuntimeError, "No form found for given year") end end end diff --git a/spec/services/documentation_generator_spec.rb b/spec/services/documentation_generator_spec.rb index 8c1d82726..5023c91f0 100644 --- a/spec/services/documentation_generator_spec.rb +++ b/spec/services/documentation_generator_spec.rb @@ -17,10 +17,11 @@ describe DocumentationGenerator do describe ":describe_hard_validations" do context "when the service is run with lettings type" do let(:log_type) { "lettings" } + let(:form) { FormHandler.instance.forms["current_lettings"] } it "creates new validation documentation records" do expect(Rails.logger).to receive(:info).with(/described/).at_least(:once) - expect { described_class.new.describe_hard_validations(client, all_validation_methods, all_helper_methods, log_type) }.to change(LogValidation, :count) + expect { described_class.new.describe_hard_validations(client, form, all_validation_methods, all_helper_methods, log_type) }.to change(LogValidation, :count) expect(LogValidation.where(validation_name: "validate_numeric_min_max").count).to eq(1) any_validation = LogValidation.first expect(any_validation.description).to eq("Validates the format.") @@ -37,12 +38,12 @@ describe DocumentationGenerator do it "calls the client" do expect(client).to receive(:chat) - described_class.new.describe_hard_validations(client, all_validation_methods, all_helper_methods, log_type) + described_class.new.describe_hard_validations(client, form, all_validation_methods, all_helper_methods, log_type) end it "skips if the validation already exists in the database" do - described_class.new.describe_hard_validations(client, all_validation_methods, all_helper_methods, log_type) - expect { described_class.new.describe_hard_validations(client, all_validation_methods, all_helper_methods, log_type) }.not_to change(LogValidation, :count) + described_class.new.describe_hard_validations(client, form, all_validation_methods, all_helper_methods, log_type) + expect { described_class.new.describe_hard_validations(client, form, all_validation_methods, all_helper_methods, log_type) }.not_to change(LogValidation, :count) end context "when the response is not a JSON" do @@ -51,7 +52,7 @@ describe DocumentationGenerator do it "raises an error" do expect(Rails.logger).to receive(:error).with(/Failed to save/).at_least(:once) expect(Rails.logger).to receive(:error).with(/Error/).at_least(:once) - described_class.new.describe_hard_validations(client, all_validation_methods, all_helper_methods, log_type) + described_class.new.describe_hard_validations(client, form, all_validation_methods, all_helper_methods, log_type) end end @@ -61,17 +62,18 @@ describe DocumentationGenerator do it "raises an error" do expect(Rails.logger).to receive(:error).with(/Failed to save/).at_least(:once) expect(Rails.logger).to receive(:error).with(/Error/).at_least(:once) - described_class.new.describe_hard_validations(client, all_validation_methods, all_helper_methods, log_type) + described_class.new.describe_hard_validations(client, form, all_validation_methods, all_helper_methods, log_type) end end end context "when the service is run with sales type" do let(:log_type) { "sales" } + let(:form) { FormHandler.instance.forms["current_sales"] } it "creates new validation documentation records" do expect(Rails.logger).to receive(:info).with(/described/).at_least(:once) - expect { described_class.new.describe_hard_validations(client, all_validation_methods, all_helper_methods, log_type) }.to change(LogValidation, :count) + expect { described_class.new.describe_hard_validations(client, form, all_validation_methods, all_helper_methods, log_type) }.to change(LogValidation, :count) expect(LogValidation.where(validation_name: "validate_numeric_min_max").count).to eq(1) any_validation = LogValidation.first expect(any_validation.description).to eq("Validates the format.") @@ -97,9 +99,10 @@ describe DocumentationGenerator do context "when the service is run for lettings" do let(:log_type) { "lettings" } + let(:form) { FormHandler.instance.forms["current_lettings"] } it "creates new validation documentation records" do - expect { described_class.new.describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type) }.to change(LogValidation, :count) + expect { described_class.new.describe_soft_validations(client, form, all_validation_methods, all_helper_methods, log_type) }.to change(LogValidation, :count) expect(LogValidation.where(validation_name: "rent_soft_validation_triggered?").count).to be_positive any_validation = LogValidation.first expect(any_validation.description).to eq("Validates the format.") @@ -115,21 +118,22 @@ describe DocumentationGenerator do it "calls the client" do expect(client).to receive(:chat) - described_class.new.describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type) + described_class.new.describe_soft_validations(client, form, all_validation_methods, all_helper_methods, log_type) end it "skips if the validation already exists in the database" do - described_class.new.describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type) - expect { described_class.new.describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type) }.not_to change(LogValidation, :count) + described_class.new.describe_soft_validations(client, form, all_validation_methods, all_helper_methods, log_type) + expect { described_class.new.describe_soft_validations(client, form, all_validation_methods, all_helper_methods, log_type) }.not_to change(LogValidation, :count) end end context "when the service is run for sales" do let(:log_type) { "sales" } + let(:form) { FormHandler.instance.forms["current_sales"] } let(:all_validation_methods) { ["income2_outside_soft_range_for_ecstat?"] } it "creates new validation documentation records" do - expect { described_class.new.describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type) }.to change(LogValidation, :count) + expect { described_class.new.describe_soft_validations(client, form, all_validation_methods, all_helper_methods, log_type) }.to change(LogValidation, :count) expect(LogValidation.where(validation_name: "income2_outside_soft_range_for_ecstat?").count).to be_positive any_validation = LogValidation.first expect(any_validation.description).to eq("Validates the format.")