diff --git a/app/services/documentation_generator.rb b/app/services/documentation_generator.rb index 999216d6d..c2005f320 100644 --- a/app/services/documentation_generator.rb +++ b/app/services/documentation_generator.rb @@ -8,10 +8,14 @@ class DocumentationGenerator include Validations::LocalAuthorityValidations 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}"] - + include Validations::SetupValidations + include Validations::HouseholdValidations + include Validations::PropertyValidations + include Validations::FinancialValidations + include Validations::TenancyValidations + include Validations::DateValidations + + 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,9 +73,9 @@ 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| + all_validation_methods.each do |meth| validation_source = method(meth).source helper_methods_source = all_helper_methods.map { |helper_method| if validation_source.include?(helper_method.to_s) @@ -87,20 +91,34 @@ 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 + def validation_and_helper_methods(validation_classes) + all_validation_methods = validation_classes.map(&:instance_methods).flatten.select { |method| method.starts_with?("validate_") }.uniq + all_methods = validation_classes.map { |x| x.instance_methods + x.private_instance_methods }.flatten.uniq + all_helper_methods = all_methods - all_validation_methods + [all_validation_methods, all_helper_methods] + end + + def get_soft_sales_methods + all_helper_methods = Validations::SoftValidations.private_instance_methods + Validations::Sales::SoftValidations.private_instance_methods + all_validation_methods = Validations::SoftValidations.instance_methods + Validations::Sales::SoftValidations.instance_methods + [all_helper_methods, all_validation_methods] + end + + def get_soft_lettings_methods + all_helper_methods = Validations::SoftValidations.private_instance_methods + all_validation_methods = Validations::SoftValidations.instance_methods + [all_helper_methods, all_validation_methods] + end + private def describe_hard_validation(client, meth, validation_source, helper_methods_source, form, file_path) @@ -109,7 +127,7 @@ private begin client.chat( parameters: { - model: "gpt-3.5-turbo", + model: "gpt-4o-mini", messages: [ { role: "system", @@ -201,7 +219,7 @@ private def soft_validation_description(client, meth, validation_source, helper_methods_source) client.chat( parameters: { - model: "gpt-3.5-turbo", + model: "gpt-4o-mini", messages: [ { role: "system", diff --git a/lib/tasks/generate_lettings_documentation.rake b/lib/tasks/generate_lettings_documentation.rake index e87fd8dca..5c2ff6f20 100644 --- a/lib/tasks/generate_lettings_documentation.rake +++ b/lib/tasks/generate_lettings_documentation.rake @@ -1,57 +1,68 @@ 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 - include Validations::PropertyValidations - include Validations::FinancialValidations - include Validations::TenancyValidations - include Validations::DateValidations - include Validations::LocalAuthorityValidations - all_validation_methods = public_methods.select { |method| method.starts_with?("validate_") } - all_methods = [Validations::SetupValidations, - Validations::HouseholdValidations, - Validations::PropertyValidations, - Validations::FinancialValidations, - Validations::TenancyValidations, - Validations::DateValidations, - 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") + + documentation_generator = DocumentationGenerator.new + validation_classes = [Validations::SetupValidations, + Validations::HouseholdValidations, + Validations::PropertyValidations, + Validations::FinancialValidations, + Validations::TenancyValidations, + Validations::DateValidations, + Validations::LocalAuthorityValidations] + all_validation_methods, all_helper_methods = documentation_generator.validation_and_helper_methods(validation_classes) + + documentation_generator.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 - include Validations::SoftValidations + task :describe_soft_lettings_validations, %i[year] => :environment do |_task, args| + form_year = args[:year]&.to_i + raise "Usage: rake generate_lettings_documentation:describe_soft_lettings_validations['year']" if form_year.blank? - client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"]) + form = FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(form_year, "lettings")] + raise "No form found for given year" if form.blank? - all_helper_methods = Validations::SoftValidations.private_instance_methods - all_validation_methods = Validations::SoftValidations.instance_methods + client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"]) + documentation_generator = DocumentationGenerator.new + all_helper_methods, all_validation_methods = documentation_generator.get_soft_lettings_methods - DocumentationGenerator.new.describe_soft_validations(client, all_validation_methods, all_helper_methods, "lettings") + documentation_generator.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/lib/tasks/generate_sales_documentation.rake b/lib/tasks/generate_sales_documentation.rake index c595c4f18..4a7ff7568 100644 --- a/lib/tasks/generate_sales_documentation.rake +++ b/lib/tasks/generate_sales_documentation.rake @@ -1,56 +1,65 @@ namespace :generate_sales_documentation do desc "Generate documentation for hard sales validations" - task describe_sales_validations: :environment do + task :describe_sales_validations, %i[year] => :environment do |_task, args| + form_year = args[:year]&.to_i + raise "Usage: rake generate_sales_documentation:describe_sales_validations['year']" if form_year.blank? + + form = FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(form_year, "sales")] + raise "No form found for given year" if form.blank? + client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"]) - include Validations::Sales::SetupValidations - include Validations::Sales::HouseholdValidations - include Validations::Sales::PropertyValidations - include Validations::Sales::FinancialValidations - include Validations::Sales::SaleInformationValidations - include Validations::SharedValidations - include Validations::LocalAuthorityValidations - all_validation_methods = public_methods.select { |method| method.starts_with?("validate_") } - all_methods = [Validations::Sales::SetupValidations, - Validations::Sales::HouseholdValidations, - Validations::Sales::PropertyValidations, - Validations::Sales::FinancialValidations, - Validations::Sales::SaleInformationValidations, - Validations::SharedValidations, - 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, "sales") + + documentation_generator = DocumentationGenerator.new + validation_classes = [Validations::Sales::SetupValidations, + Validations::Sales::HouseholdValidations, + Validations::Sales::PropertyValidations, + Validations::Sales::FinancialValidations, + Validations::Sales::SaleInformationValidations, + Validations::SharedValidations, + Validations::LocalAuthorityValidations] + all_validation_methods, all_helper_methods = documentation_generator.validation_and_helper_methods(validation_classes) + documentation_generator.describe_hard_validations(client, form, all_validation_methods, all_helper_methods, "sales") end desc "Generate documentation for soft sales validations" - task describe_soft_sales_validations: :environment do - include Validations::SoftValidations - include Validations::Sales::SoftValidations + task :describe_soft_sales_validations, %i[year] => :environment do |_task, args| + form_year = args[:year]&.to_i + raise "Usage: rake generate_sales_documentation:describe_soft_sales_validations['year']" if form_year.blank? + + form = FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(form_year, "sales")] + 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 + Validations::Sales::SoftValidations.private_instance_methods - all_validation_methods = Validations::SoftValidations.instance_methods + Validations::Sales::SoftValidations.instance_methods + documentation_generator = DocumentationGenerator.new + all_helper_methods, all_validation_methods = documentation_generator.get_soft_sales_methods - DocumentationGenerator.new.describe_soft_validations(client, all_validation_methods, all_helper_methods, "sales") + documentation_generator.describe_soft_validations(client, form, all_validation_methods, all_helper_methods, "sales") end desc "Generate documentation for hard bu sales validations" - task describe_bu_sales_validations: :environment do + task :describe_bu_sales_validations, %i[year] => :environment do |_task, args| + form_year = args[:year]&.to_i + raise "Usage: rake generate_sales_documentation:describe_bu_sales_validations['year']" if form_year.blank? + + form = FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(form_year, "sales")] + raise "No form found for given year" if form.blank? + + row_parser_class = "BulkUpload::Sales::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, "sales")], BulkUpload::Sales::Year2023::RowParser], - [FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(2024, "sales")], BulkUpload::Sales::Year2024::RowParser]].each do |form, row_parser_class| - 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 - field_mapping_for_errors = row_parser_class.new.send("field_mapping_for_errors") + 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 + 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, "sales") - end + DocumentationGenerator.new.describe_bu_validations(client, form, row_parser_class, all_validation_methods, all_helper_methods, field_mapping_for_errors, "sales") end desc "Generate documentation for sales numeric validations" - task add_numeric_sales_validations: :environment do - form = FormHandler.instance.forms["current_sales"] + task :add_numeric_sales_validations, %i[year] => :environment do |_task, args| + form_year = args[:year]&.to_i + raise "Usage: rake generate_sales_documentation:add_numeric_sales_validations['year']" if form_year.blank? + + form = FormHandler.instance.forms[FormHandler.instance.form_name_from_start_year(form_year, "sales")] + 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..d7c39496d 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, validation_and_helper_methods: []) } + 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, get_soft_lettings_methods: []) } + 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/lib/tasks/generate_sales_documentation_spec.rb b/spec/lib/tasks/generate_sales_documentation_spec.rb index 4626f1a42..fb35203b1 100644 --- a/spec/lib/tasks/generate_sales_documentation_spec.rb +++ b/spec/lib/tasks/generate_sales_documentation_spec.rb @@ -6,14 +6,24 @@ RSpec.describe "generate_sales_documentation" do subject(:task) { Rake::Task["generate_sales_documentation:add_numeric_sales_validations"] } before do + Timecop.freeze(Time.zone.local(2025, 1, 1)) + Singleton.__init__(FormHandler) + allow(FormHandler.instance).to receive(:forms).and_return({ "current_sales" => FormHandler.instance.forms["current_sales"], "previous_sales" => "2023_form", "next_sales" => "2025_form" }) Rake.application.rake_require("tasks/generate_sales_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_sales").and_return(FormHandler.instance.forms["current_sales"]) + + 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_sales_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_sales_documentation:add_numeric_sales_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_sales_validations", type: :task do + subject(:task) { Rake::Task["generate_sales_documentation:describe_sales_validations"] } + + let(:documentation_generator) { instance_double(DocumentationGenerator, describe_bu_validations: nil, validation_and_helper_methods: []) } + 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_sales" => "2024_form", "previous_sales" => "2023_form", "next_sales" => "2025_form" }) + Rake.application.rake_require("tasks/generate_sales_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_sales").and_return("2025_form") + expect(documentation_generator).to receive(:describe_hard_validations).with(client, "2025_form", anything, anything, "sales") + + task.invoke("2025") + end + + it "gets the correct form for current year" do + allow(FormHandler.instance.forms).to receive(:[]).with("current_sales").and_return("2024_form") + expect(documentation_generator).to receive(:describe_hard_validations).with(client, "2024_form", anything, anything, "sales") + 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_sales_documentation:describe_sales_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_sales_validations", type: :task do + subject(:task) { Rake::Task["generate_sales_documentation:describe_bu_sales_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_sales" => "2024_form", "previous_sales" => "2023_form", "next_sales" => "2025_form" }) + Rake.application.rake_require("tasks/generate_sales_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_sales").and_return("2025_form") + expect(documentation_generator).to receive(:describe_bu_validations).with(client, "2025_form", anything, anything, anything, anything, "sales") + + task.invoke("2025") + end + + it "gets the correct form for current year" do + allow(FormHandler.instance.forms).to receive(:[]).with("current_sales").and_return("2024_form") + expect(documentation_generator).to receive(:describe_bu_validations).with(client, "2024_form", anything, anything, anything, anything, "sales") + 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_sales_documentation:describe_bu_sales_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_sales_validations", type: :task do + subject(:task) { Rake::Task["generate_sales_documentation:describe_soft_sales_validations"] } + + let(:documentation_generator) { instance_double(DocumentationGenerator, describe_bu_validations: nil, get_soft_sales_methods: []) } + 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_sales" => "2024_form", "previous_sales" => "2023_form", "next_sales" => "2025_form" }) + Rake.application.rake_require("tasks/generate_sales_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_sales").and_return("2025_form") + expect(documentation_generator).to receive(:describe_soft_validations).with(client, "2025_form", anything, anything, "sales") + + task.invoke("2025") + end + + it "gets the correct form for current year" do + allow(FormHandler.instance.forms).to receive(:[]).with("current_sales").and_return("2024_form") + expect(documentation_generator).to receive(:describe_soft_validations).with(client, "2024_form", anything, anything, "sales") + 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_sales_documentation:describe_soft_sales_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.")