diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8ce988ccf..4b01bf4cf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,6 +7,6 @@ version: 2 updates: - package-ecosystem: "bundler" # See documentation for possible values directory: "/" # Location of package manifests - open-pull-requests-limit: 5 + open-pull-requests-limit: 15 schedule: interval: "daily" diff --git a/Dockerfile b/Dockerfile index 0269eac74..dba93ff41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,7 +40,7 @@ ENTRYPOINT ["/usr/bin/dumb-init", "--", "./docker-entrypoint.sh"] ### FROM development AS builder -ENV BUNDLER_VERSION='2.5.9' +ENV BUNDLER_VERSION='2.6.0' ARG bundler_opts COPY --chown=gi-bill-data-service:gi-bill-data-service . . diff --git a/Gemfile b/Gemfile index 7f99e0eef..28692e9ca 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'rails', '7.1.3.4' +gem 'rails', '7.1.5.1' gem 'active_model_serializers', '~> 0.10.14' # JSON API gem 'activerecord-import' # Mass importing of CSV data @@ -34,7 +34,7 @@ gem 'rack', '>= 2.2.8.1' gem 'rack-cors', require: 'rack/cors' # CORS gem 'rails-html-sanitizer', '>= 1.4.4' gem 'rainbow' -gem 'rexml', '~> 3.3.6' +gem 'rexml', '~> 3.3.9' gem 'roo', '~> 2.10' gem 'roo-xls', '~> 1.2' gem 'ruby-saml' diff --git a/Gemfile.lock b/Gemfile.lock index 18c334610..36fb0d56a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,35 +18,35 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.1.3.4) - actionpack (= 7.1.3.4) - activesupport (= 7.1.3.4) + actioncable (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.3.4) - actionpack (= 7.1.3.4) - activejob (= 7.1.3.4) - activerecord (= 7.1.3.4) - activestorage (= 7.1.3.4) - activesupport (= 7.1.3.4) + actionmailbox (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.3.4) - actionpack (= 7.1.3.4) - actionview (= 7.1.3.4) - activejob (= 7.1.3.4) - activesupport (= 7.1.3.4) + actionmailer (7.1.5.1) + actionpack (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.2) - actionpack (7.1.3.4) - actionview (= 7.1.3.4) - activesupport (= 7.1.3.4) + actionpack (7.1.5.1) + actionview (= 7.1.5.1) + activesupport (= 7.1.5.1) nokogiri (>= 1.8.5) racc rack (>= 2.2.4) @@ -54,15 +54,15 @@ GEM rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.3.4) - actionpack (= 7.1.3.4) - activerecord (= 7.1.3.4) - activestorage (= 7.1.3.4) - activesupport (= 7.1.3.4) + actiontext (7.1.5.1) + actionpack (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.3.4) - activesupport (= 7.1.3.4) + actionview (7.1.5.1) + activesupport (= 7.1.5.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) @@ -72,14 +72,14 @@ GEM activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (7.1.3.4) - activesupport (= 7.1.3.4) + activejob (7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.3.6) - activemodel (7.1.3.4) - activesupport (= 7.1.3.4) - activerecord (7.1.3.4) - activemodel (= 7.1.3.4) - activesupport (= 7.1.3.4) + activemodel (7.1.5.1) + activesupport (= 7.1.5.1) + activerecord (7.1.5.1) + activemodel (= 7.1.5.1) + activesupport (= 7.1.5.1) timeout (>= 0.4.0) activerecord-import (1.7.0) activerecord (>= 4.2) @@ -90,21 +90,24 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 2.0.8, < 4) railties (>= 6.1) - activestorage (7.1.3.4) - actionpack (= 7.1.3.4) - activejob (= 7.1.3.4) - activerecord (= 7.1.3.4) - activesupport (= 7.1.3.4) + activestorage (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activesupport (= 7.1.5.1) marcel (~> 1.0) - activesupport (7.1.3.4) + activesupport (7.1.5.1) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) @@ -115,6 +118,7 @@ GEM thread_safe (~> 0.3, >= 0.3.1) base64 (0.2.0) bcrypt (3.1.20) + benchmark (0.4.0) bigdecimal (3.1.8) bindex (0.8.1) bootsnap (1.18.3) @@ -122,7 +126,7 @@ GEM brakeman (6.1.2) racc builder (3.3.0) - bundler-audit (0.9.1) + bundler-audit (0.9.2) bundler (>= 1.2.0, < 3) thor (~> 1.0) byebug (11.1.3) @@ -154,7 +158,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.4) + date (3.4.1) deep_merge (1.2.2) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) @@ -168,7 +172,7 @@ GEM docile (1.4.0) drb (2.2.1) erubi (1.13.0) - execjs (2.8.1) + execjs (2.10.0) factory_bot (6.4.6) activesupport (>= 5.0.0) factory_bot_rails (6.4.3) @@ -232,11 +236,11 @@ GEM guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) headless (2.3.1) - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) ice_nine (0.11.2) - io-console (0.7.2) - irb (1.14.0) + io-console (0.8.0) + irb (1.14.2) rdoc (>= 4.0.0) reline (>= 0.4.2) jquery-rails (4.6.0) @@ -255,7 +259,8 @@ GEM listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.22.0) + logger (1.6.3) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) lumberjack (1.2.8) @@ -273,13 +278,13 @@ GEM mini_mime (1.1.5) mini_racer (0.12.0) libv8-node (~> 21.7.2.0) - minitest (5.25.0) + minitest (5.25.4) msgpack (1.7.2) multi_json (1.15.0) multipart-post (2.4.0) mutex_m (0.2.0) nenv (0.3.0) - net-imap (0.4.12) + net-imap (0.5.2) date net-protocol net-pop (0.1.2) @@ -289,12 +294,12 @@ GEM net-smtp (0.5.0) net-protocol newrelic_rpm (9.13.0) - nio4r (2.7.3) - nokogiri (1.16.5-aarch64-linux) + nio4r (2.7.4) + nokogiri (1.16.8-aarch64-linux) racc (~> 1.4) - nokogiri (1.16.5-arm64-darwin) + nokogiri (1.16.8-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.5-x86_64-linux) + nokogiri (1.16.8-x86_64-linux) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) @@ -302,48 +307,48 @@ GEM oj (3.16.3) bigdecimal (>= 3.0) orm_adapter (0.5.0) - parallel (1.25.1) - parallel_tests (4.7.1) + parallel (1.26.3) + parallel_tests (4.7.2) parallel - parser (3.3.3.0) + parser (3.3.5.0) ast (~> 2.4.1) racc - pg (1.5.6) + pg (1.5.8) pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) pry-nav (1.0.0) pry (>= 0.9.10, < 0.15) - psych (5.1.2) + psych (5.2.1) + date stringio public_suffix (5.0.4) puma (6.4.3) nio4r (~> 2.0) racc (1.8.1) - rack (3.1.7) + rack (3.1.8) rack-cors (2.0.2) rack (>= 2.0.0) rack-session (2.0.0) rack (>= 3.0.0) rack-test (2.1.0) rack (>= 1.3) - rackup (2.1.0) + rackup (2.2.1) rack (>= 3) - webrick (~> 1.8) - rails (7.1.3.4) - actioncable (= 7.1.3.4) - actionmailbox (= 7.1.3.4) - actionmailer (= 7.1.3.4) - actionpack (= 7.1.3.4) - actiontext (= 7.1.3.4) - actionview (= 7.1.3.4) - activejob (= 7.1.3.4) - activemodel (= 7.1.3.4) - activerecord (= 7.1.3.4) - activestorage (= 7.1.3.4) - activesupport (= 7.1.3.4) + rails (7.1.5.1) + actioncable (= 7.1.5.1) + actionmailbox (= 7.1.5.1) + actionmailer (= 7.1.5.1) + actionpack (= 7.1.5.1) + actiontext (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activemodel (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) bundler (>= 1.15.0) - railties (= 7.1.3.4) + railties (= 7.1.5.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -352,12 +357,12 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) - nokogiri (~> 1.14) - railties (7.1.3.4) - actionpack (= 7.1.3.4) - activesupport (= 7.1.3.4) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) irb rackup (>= 1.0.0) rake (>= 12.2) @@ -368,16 +373,15 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rdoc (6.7.0) + rdoc (6.9.1) psych (>= 4.0.0) regexp_parser (2.9.2) - reline (0.5.9) + reline (0.6.0) io-console (~> 0.5) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.3.6) - strscan + rexml (3.3.9) roo (2.10.1) nokogiri (~> 1) rubyzip (>= 1.3.0, < 3.0.0) @@ -389,18 +393,18 @@ GEM rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) + rspec-core (3.13.1) rspec-support (~> 3.13.0) - rspec-expectations (3.13.1) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.4) - actionpack (>= 6.1) - activesupport (>= 6.1) - railties (>= 6.1) + rspec-rails (7.0.1) + actionpack (>= 7.0) + activesupport (>= 7.0) + railties (>= 7.0) rspec-core (~> 3.13) rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) @@ -417,16 +421,16 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.3) + rubocop-ast (1.32.3) parser (>= 3.3.1.0) rubocop-capybara (2.20.0) rubocop (~> 1.41) rubocop-factory_bot (2.25.1) rubocop (~> 1.41) - rubocop-rails (2.25.1) + rubocop-rails (2.27.0) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) + rubocop (>= 1.52.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) rubocop-rspec (2.29.2) rubocop (~> 1.40) @@ -460,6 +464,7 @@ GEM script_utils (0.0.4) scss_lint (0.60.0) sass (~> 3.5, >= 3.5.5) + securerandom (0.4.1) selenium-webdriver (4.16.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) @@ -488,21 +493,20 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - stringio (3.1.1) + stringio (3.1.2) strong_migrations (1.8.0) activerecord (>= 5.2) - strscan (3.1.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - terser (1.2.3) + terser (1.2.4) execjs (>= 0.3.0, < 3) - thor (1.3.1) + thor (1.3.2) thread_safe (0.3.6) tilt (2.1.0) - timeout (0.4.1) + timeout (0.4.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.5.0) + unicode-display_width (2.6.0) vcr (6.2.0) virtus (2.0.0) axiom-types (~> 0.1) @@ -518,7 +522,6 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webrick (1.8.2) websocket (1.2.10) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -526,11 +529,12 @@ GEM will_paginate (4.0.1) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.17) + zeitwerk (2.7.1) PLATFORMS aarch64-linux arm64-darwin-23 + arm64-darwin-24 x86_64-linux DEPENDENCIES @@ -576,11 +580,11 @@ DEPENDENCIES puma (~> 6.4.3) rack (>= 2.2.8.1) rack-cors - rails (= 7.1.3.4) + rails (= 7.1.5.1) rails-controller-testing rails-html-sanitizer (>= 1.4.4) rainbow - rexml (~> 3.3.6) + rexml (~> 3.3.9) roo (~> 2.10) roo-xls (~> 1.2) rspec-rails @@ -607,4 +611,4 @@ DEPENDENCIES will_paginate BUNDLED WITH - 2.5.9 + 2.6.0 diff --git a/README.md b/README.md index 621a7bf48..94c995a38 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Secondarily, institution information may be exported as a CSV for regulatory rep GIDS data is accessible via an API intended for use by the GI Bill Comparison Tool client (**GIBCT**), which is part of the `vets-api` and `vets-website` applications. -### Data Modes and Versions +### Data Modes and Versions GIDS profile data is logically partitioned in two modes: **preview** mode and **production** mode. In preview mode the data retrieved via the API has not yet been completely processed and built out by the InstitutionBuilder model. In contrast, production mode is the actual data pushed to **GIBCT** for public consumption. diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb index 0da14bdad..ff3b7602b 100644 --- a/app/controllers/dashboards_controller.rb +++ b/app/controllers/dashboards_controller.rb @@ -210,7 +210,7 @@ def download_csv(class_nm) def unzip_csv(class_nm) # Some downloads do are not a zip file, so skip and return true - return true if class_nm.eql?('Hcm') || class_nm.eql?('EightKey') || class_nm.eql?('Mou') + return true if class_nm.eql?('Hcm') || class_nm.eql?('EightKey') || class_nm.eql?('Mou') || class_nm.eql?('Vsoc') ZipFileUtils::Unzipper.new.unzip_the_file end diff --git a/app/controllers/v0/institution_programs_controller.rb b/app/controllers/v0/institution_programs_controller.rb index 4bcf08913..a2b48e348 100644 --- a/app/controllers/v0/institution_programs_controller.rb +++ b/app/controllers/v0/institution_programs_controller.rb @@ -29,7 +29,7 @@ def index render json: search_results .search_order(@query) - .page(page), meta: @meta + .then { |results| pagination_for(results) }, meta: @meta end private @@ -40,7 +40,9 @@ def normalized_query_params query[:name].try(:strip!) query[:name].try(:downcase!) query[:preferred_provider].try(:downcase!) + query[:disable_pagination].try(:downcase!) query[:provider].try(:upcase!) + query[:facility_code].try(:upcase!) %i[state country type].each do |k| query[k].try(:upcase!) end @@ -85,6 +87,7 @@ def filter_results(relation) [ %i[program_type type], %i[institutions.institution provider], + %i[institutions.facility_code facility_code], %w[institutions.physical_country country], %w[institutions.physical_state state], %w[institutions.preferred_provider preferred_provider] @@ -116,5 +119,9 @@ def add_active_search_facets(raw_facets) add_country_search_facet(raw_facets) raw_facets end + + def pagination_for(results) + @query[:disable_pagination] == 'true' ? results : results.page(page) + end end end diff --git a/app/controllers/v1/institutions_controller.rb b/app/controllers/v1/institutions_controller.rb index d842e9b41..d6d8e267d 100644 --- a/app/controllers/v1/institutions_controller.rb +++ b/app/controllers/v1/institutions_controller.rb @@ -50,9 +50,15 @@ def index def location @query ||= normalized_query_params - location_results = Institution.approved_institutions(@version).location_search(@query).filter_result_v1(@query) - results = location_results.location_select(@query).location_order + # Start with location-based search + location_results = Institution.approved_institutions(@version) + .location_search(@query) + .filter_result_v1(@query) + + # Add name search if name parameter is present + location_results = location_results.search_v1(name: @query[:name]) if @query[:name].present? + results = location_results.location_select(@query).location_order results = results.filter_high_school if @query[:excluded_school_types]&.include?('HIGH SCHOOL') @meta = { @@ -66,6 +72,35 @@ def location meta: @meta end + # GET /v1/gi/institutions/search?description=nursing&latitude=42.3601&longitude=-71.0589 + def program + @query ||= normalized_query_params + + # Start with fast location search + location_results = Institution.approved_institutions(@version) + .location_search(@query) + .filter_result_v1(@query) + + # Then filter those results by program description + results = location_results + .joins(:institution_programs) + .where('institution_programs.description ILIKE ?', "%#{@query[:description]}%") + .location_select(@query) + .location_order + + results = results.filter_high_school if @query[:excluded_school_types]&.include?('HIGH SCHOOL') + + @meta = { + version: @version, + count: results.unscope(:select).count, # Add unscope + facets: facets(location_results) + } + + render json: results, + each_serializer: InstitutionSearchResultSerializer, + meta: @meta + end + # GET /v1/institutions?facility_codes=1,2,3,4 # Search by facility code and return using InstitutionCompareSerializer def facility_codes @@ -125,7 +160,7 @@ def normalized_query_params end %i[name category student_veteran_group yellow_ribbon_scholarship principles_of_excellence eight_keys_to_veteran_success stem_offered independent_study priority_enrollment - online_only distance_learning location].each do |k| + online_only distance_learning].each do |k| query[k].try(:downcase!) end %i[latitude longitude distance].each do |k| diff --git a/app/models/crosswalk_issue.rb b/app/models/crosswalk_issue.rb index 0036f411e..9389ade16 100644 --- a/app/models/crosswalk_issue.rb +++ b/app/models/crosswalk_issue.rb @@ -14,10 +14,57 @@ class CrosswalkIssue < ApplicationRecord scope :by_issue_type, ->(n) { where(issue_type: n) } + scope :by_domestic_crosswalks, lambda { + joins( + 'JOIN crosswalks crs on crosswalk_issues.crosswalk_id = crs.id ' \ + " AND RIGHT(crs.facility_code, 2) ~ '^[0-9]{2}$' " \ + ' AND CAST(right(crs.facility_code, 2) as integer) < 51 ' + ).merge(by_issue_type(PARTIAL_MATCH_TYPE)) + } + + scope :by_domestic_weams, lambda { + joins( + 'JOIN weams wms on crosswalk_issues.weam_id = wms.id ' \ + " AND RIGHT(wms.facility_code, 2) ~ '^[0-9]{2}$' " \ + ' AND CAST(right(wms.facility_code, 2) as integer) < 51 ' + ).merge(by_issue_type(PARTIAL_MATCH_TYPE)) + } + + scope :by_domestic_iped_hds, lambda { + joins( + 'JOIN ipeds_hds ihs on crosswalk_issues.ipeds_hd_id = ihs.id ' \ + 'JOIN weams iws on ihs.cross = iws.cross ' \ + " AND RIGHT(iws.facility_code, 2) ~ '^[0-9]{2}$' " \ + ' AND CAST(right(iws.facility_code, 2) as integer) < 51' + ).merge(by_issue_type(PARTIAL_MATCH_TYPE)) + } + + # The issue here is activerecord doesn't have a union clause per se. + # https://stackoverflow.com/questions/6686920/activerecord-query-union + # See solution by Vlad Hilko near the bottom of the page. However, this + # soltion only works when you have two pieces to the union. For more, you + # have to chain them as described in this solution describe by Sebastian Palma + # https://stackoverflow.com/questions/59294114/build-triple-union-query-using-arel-rails-5 + def self.domestic_partial_matches + domestic_crosswalks = by_domestic_crosswalks.arel + domestic_weams = by_domestic_weams.arel + domestic_iped_hds = by_domestic_iped_hds.arel + + subquery = Arel::Nodes::As.new( + Arel::Nodes::Union.new( + Arel::Nodes::Union.new( + domestic_crosswalks, domestic_weams + ), domestic_iped_hds + ), CrosswalkIssue.arel_table + ) + + from(subquery) + end + # class methods def self.partials includes(:crosswalk, :ipeds_hd, weam: :arf_gi_bill) - .by_issue_type(CrosswalkIssue::PARTIAL_MATCH_TYPE) + .domestic_partial_matches .order('arf_gi_bills.gibill desc nulls last, weams.institution, weams.facility_code') end diff --git a/app/models/institution_builder.rb b/app/models/institution_builder.rb index cdde063cf..94c707019 100644 --- a/app/models/institution_builder.rb +++ b/app/models/institution_builder.rb @@ -738,8 +738,8 @@ def self.add_sec103(version_id) Institution.connection.execute(InstitutionProgram.send(:sanitize_sql, [str])) end - # edu_programs.length_in_weeks is being used twice because - # it is a short term fix to an issue that they aren't sure how we should fix + # Previously included join on edu_programs table + # However, edu_programs feed determined to be defunct for time being def self.build_institution_programs(version_id) log_info_status 'Creating Institution Program rows' str = <<-SQL @@ -749,19 +749,6 @@ def self.build_institution_programs(version_id) full_time_undergraduate, graduate, full_time_modifier, - length_in_hours, - school_locale, - provider_website, - provider_email_address, - phone_area_code, - phone_number, - student_vet_group, - student_vet_group_website, - vet_success_name, - vet_success_email, - vet_tec_program, - tuition_amount, - length_in_weeks, institution_id ) SELECT @@ -770,34 +757,11 @@ def self.build_institution_programs(version_id) full_time_undergraduate, graduate, full_time_modifier, - length_in_weeks, - school_locale, - provider_website, - provider_email_address, - phone_area_code, - phone_number, - student_vet_group, - student_vet_group_website, - vet_success_name, - vet_success_email, - vet_tec_program, - tuition_amount, - length_in_weeks, i.id FROM programs p - INNER JOIN edu_programs e ON p.facility_code = e.facility_code - AND LOWER(description) = LOWER(vet_tec_program) - AND vet_tec_program IS NOT NULL INNER JOIN institutions i ON p.facility_code = i.facility_code WHERE i.version_id = #{version_id} - AND i.approved = true;; - - UPDATE institution_programs SET - length_in_hours = 0, - length_in_weeks = 0 - WHERE id IN ( - SELECT MIN(id) FROM institution_programs GROUP BY UPPER(description), institution_id HAVING COUNT(*) > 1 - ); + AND i.approved = true; DELETE FROM institution_programs WHERE id NOT IN ( SELECT MIN(id) FROM institution_programs GROUP BY UPPER(description), institution_id diff --git a/app/models/vsoc.rb b/app/models/vsoc.rb index 2dc56cd6d..fba3e407c 100644 --- a/app/models/vsoc.rb +++ b/app/models/vsoc.rb @@ -10,5 +10,7 @@ class Vsoc < ImportableRecord 'vetsuccess_email' => { column: :vetsuccess_email, converter: Converters::BaseConverter } }.freeze + API_SOURCE = 'https://vbaw.vba.va.gov/EDUCATION/job_aids/documents/' + validates :facility_code, presence: true end diff --git a/app/serializers/institution_profile_serializer.rb b/app/serializers/institution_profile_serializer.rb index e20a7f0c5..055510da3 100644 --- a/app/serializers/institution_profile_serializer.rb +++ b/app/serializers/institution_profile_serializer.rb @@ -93,6 +93,7 @@ class InstitutionProfileSerializer < ActiveModel::Serializer attribute :stem_indicator attribute :facility_map attribute :programs + attribute :program_types attribute :versioned_school_certifying_officials attribute :count_of_caution_flags attribute :section_103_message @@ -141,6 +142,10 @@ def programs end end + def program_types + InstitutionProgram.where(institution: object).distinct.pluck(:program_type) + end + def caution_flags return [] unless object.caution_flag diff --git a/app/serializers/institution_program_serializer.rb b/app/serializers/institution_program_serializer.rb index 2a23d3abe..7716237c5 100644 --- a/app/serializers/institution_program_serializer.rb +++ b/app/serializers/institution_program_serializer.rb @@ -3,10 +3,7 @@ class InstitutionProgramSerializer < ActiveModel::Serializer attributes :program_type, :description, - :length_in_hours, - :length_in_weeks, :facility_code, - :tuition_amount, :institution_name, :city, :state, diff --git a/app/utilities/no_key_apis/no_key_api_downloader.rb b/app/utilities/no_key_apis/no_key_api_downloader.rb index 7da65f21b..69d19d452 100644 --- a/app/utilities/no_key_apis/no_key_api_downloader.rb +++ b/app/utilities/no_key_apis/no_key_api_downloader.rb @@ -12,9 +12,11 @@ class NoKeyApiDownloader 'IpedsIcAy' => 'tmp/ic2022_ay.csv', 'IpedsIcPy' => 'tmp/ic2022_py.csv', 'IpedsIc' => 'tmp/ic2022.csv', - 'Mou' => 'tmp/mou.xlsx' + 'Mou' => 'tmp/mou.xlsx', + 'Vsoc' => 'tmp/vsoc.csv' }.freeze + # Vsoc uses -k parameter to bypass SSL certificate errors API_NO_KEY_DOWNLOAD_SOURCES = { 'Accreditation' => [' -X POST', 'https://ope.ed.gov/dapip/api/downloadFiles/accreditationDataFiles'], 'AccreditationAction' => [' -X POST', 'https://ope.ed.gov/dapip/api/downloadFiles/accreditationDataFiles'], @@ -26,7 +28,8 @@ class NoKeyApiDownloader 'IpedsIc' => [' -X GET', 'https://nces.ed.gov/ipeds/datacenter/data/IC2022.zip'], 'IpedsIcAy' => [' -X GET', 'https://nces.ed.gov/ipeds/datacenter/data/IC2022_AY.zip'], 'IpedsIcPy' => [' -X GET', 'https://nces.ed.gov/ipeds/datacenter/data/IC2022_PY.zip'], - 'Mou' => [' -X GET', "'https://www.dodmou.com/Home/DownloadS3File?s3bucket=dodmou-private-ah9xbf&s3Key=participatinginstitutionslist%2Fproduction%2FInstitutionsList.xlsx'"] + 'Mou' => [' -X GET', "'https://www.dodmou.com/Home/DownloadS3File?s3bucket=dodmou-private-ah9xbf&s3Key=participatinginstitutionslist%2Fproduction%2FInstitutionsList.xlsx'"], + 'Vsoc' => [' -k -X GET', "'https://vbaw.vba.va.gov/EDUCATION/job_aids/documents/Vsoc_08132024.csv'"] }.freeze attr_accessor :class_nm, :curl_command @@ -46,7 +49,9 @@ def download_csv private def h_parm + # Vsoc uses the octet-stream header to pull down from source return '-H "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0"' if @class_nm.eql?('Hcm') + return '-H \'Content-Type: application/octet-stream\'' if @class_nm.eql?('Vsoc') '-H \'Content-Type: application/json\'' end @@ -56,6 +61,7 @@ def o_parm when 'Hcm' then '-o tmp/hcm.xlsx' when 'EightKey' then '-o tmp/eight_key.xls' when 'Mou' then '-o tmp/mou.xlsx' + when 'Vsoc' then '-o tmp/vsoc.csv' else '-o tmp/download.zip' end end diff --git a/app/views/dashboards/_issues.html.erb b/app/views/dashboards/_issues.html.erb index a1e99195e..dc0ac7bb3 100644 --- a/app/views/dashboards/_issues.html.erb +++ b/app/views/dashboards/_issues.html.erb @@ -9,7 +9,7 @@ <%= link_to crosswalk_issues_partials_path do %> <% end %> diff --git a/config/csv_file_defaults.yml b/config/csv_file_defaults.yml index 6a6f66d1b..be065328b 100644 --- a/config/csv_file_defaults.yml +++ b/config/csv_file_defaults.yml @@ -36,6 +36,7 @@ Complaint: Program: col_sep: '|' + liberal_parsing: true SchoolCertifyingOfficial: liberal_parsing: true diff --git a/config/initializers/csv_types.rb b/config/initializers/csv_types.rb index 1a8709ffd..bd3b38b4c 100644 --- a/config/initializers/csv_types.rb +++ b/config/initializers/csv_types.rb @@ -19,7 +19,7 @@ { klass: ScorecardDegreeProgram, required?: false, has_api?: true }, { klass: Sec702, required?: true }, { klass: Sva, required?: true }, - { klass: Vsoc, required?: true }, + { klass: Vsoc, required?: true, has_api?: true, no_api_key?: true }, { klass: Weam, required?: true }, { klass: CalculatorConstant, required?: false }, { klass: IpedsCipCode, required?: true }, diff --git a/config/puma.rb b/config/puma.rb index ec395047c..986043280 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -26,7 +26,6 @@ # value is 60 seconds. # -worker_timeout(60) -worker_timeout(3600) if ENV['RAILS_ENV'].eql?('development') +worker_timeout(600) preload_app! diff --git a/config/routes.rb b/config/routes.rb index 23d069e6a..8b423e2f7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -72,6 +72,11 @@ namespace :v1, defaults: { format: 'json' } do get '/calculator/constants' => 'calculator_constants#index' get '/institutions', to: 'institutions#facility_codes', constraints: lambda { |request| request.query_parameters.key?(:facility_codes) } + get '/institutions', to: 'institutions#program', constraints: lambda { |request| + request.query_parameters.key?(:description) && + request.query_parameters.key?(:latitude) && + request.query_parameters.key?(:longitude) + } get '/institutions', to: 'institutions#location', constraints: lambda { |request| request.query_parameters.key?(:latitude) && request.query_parameters.key?(:longitude) } resources :institutions, only: [:index, :show] do diff --git a/config/settings.yml b/config/settings.yml index bf67f98db..fc84f2f0b 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -1,7 +1,7 @@ active_record: batch_size: find_each: 5000 - import: 50000 + import: 1000 archiver: archive: true diff --git a/lib/tasks/security.rake b/lib/tasks/security.rake index bd98938f1..a0ec60956 100644 --- a/lib/tasks/security.rake +++ b/lib/tasks/security.rake @@ -13,7 +13,8 @@ task security: :environment do puts 'running bundle-audit to check for insecure dependencies...' exit!(1) unless Tasks::Support::ShellCommand.run('bundle-audit update') audit_result = Tasks::Support::ShellCommand.run( - 'bundle-audit check --ignore CVE-2017-8418 CVE-2024-26143 CVE-2024-27456 CVE-2024-34341 CVE-2024-28103' + 'bundle-audit check --ignore CVE-2017-8418 CVE-2024-26143 CVE-2024-27456 CVE-2024-34341 ' \ + 'CVE-2024-28103 CVE-2024-47889 CVE-2024-41128 CVE-2024-47887 CVE-2024-47888' ) puts "\n" if brakeman_result && audit_result diff --git a/spec/config/docker_spec.rb b/spec/config/docker_spec.rb index 308c6b3c1..69e463f02 100644 --- a/spec/config/docker_spec.rb +++ b/spec/config/docker_spec.rb @@ -7,7 +7,7 @@ let(:locked_bundle_version) do # seems to be depreciated with ruby version upgrade # Bundler::Definition.build('Gemfile', nil, {}).locked_bundler_version - '2.5.9' + '2.6.0' end it 'in Dockerfile' do diff --git a/spec/controllers/crosswalk_issues_controller_spec.rb b/spec/controllers/crosswalk_issues_controller_spec.rb index 7cc6bec7e..3bdd9b207 100644 --- a/spec/controllers/crosswalk_issues_controller_spec.rb +++ b/spec/controllers/crosswalk_issues_controller_spec.rb @@ -28,6 +28,7 @@ it 'orders by arf_gi_bill.gibill' do issues = assigns(:issues) issues_last = issues[issues.length - 1] + expect(issues.first.weam.arf_gi_bill.gibill).to be > issues_last.weam.arf_gi_bill.gibill end end diff --git a/spec/controllers/v0/institution_programs_controller_spec.rb b/spec/controllers/v0/institution_programs_controller_spec.rb index 671d4aea8..a711650d4 100644 --- a/spec/controllers/v0/institution_programs_controller_spec.rb +++ b/spec/controllers/v0/institution_programs_controller_spec.rb @@ -186,6 +186,24 @@ expect(response).to match_response_schema('institution_programs') end + it 'filters by uppercase facility code' do + create(:institution, version_id: Version.last.id, facility_code: 'ABCD1234') + create(:institution_program, institution_id: Institution.last.id) + get(:index, params: { facility_code: Institution.last.facility_code }) + expect(JSON.parse(response.body)['data'].count).to eq(1) + expect(response.media_type).to eq('application/json') + expect(response).to match_response_schema('institution_programs') + end + + it 'filters by lowercase facility code' do + create(:institution, version_id: Version.last.id, facility_code: 'ABCD1234') + create(:institution_program, institution_id: Institution.last.id) + get(:index, params: { facility_code: Institution.last.facility_code.downcase }) + expect(JSON.parse(response.body)['data'].count).to eq(1) + expect(response.media_type).to eq('application/json') + expect(response).to match_response_schema('institution_programs') + end + it 'filter by lowercase state returns results' do get(:index, params: { state: 'ny' }) expect(JSON.parse(response.body)['data'].count).to eq(3) @@ -283,5 +301,41 @@ get(:index, params: { name: 'testville, ak' }) expect(JSON.parse(response.body)['data'].count).to eq(1) end + + context 'when configuring pagination' do + context 'when enabled' do + it 'enables pagination by default' do + get(:index) + expect(JSON.parse(response.body)['links']).not_to be_nil + end + + it 'enables pagination with invalid query parameter' do + get(:index, params: { disable_pagination: 'invalid' }) + expect(JSON.parse(response.body)['links']).not_to be_nil + end + + it 'enables pagination with a lowercase query parameter' do + get(:index, params: { disable_pagination: 'false' }) + expect(JSON.parse(response.body)['links']).not_to be_nil + end + + it 'enables pagination with an uppercase query parameter' do + get(:index, params: { disable_pagination: 'False' }) + expect(JSON.parse(response.body)['links']).not_to be_nil + end + end + + context 'when disabled' do + it 'disables pagination with a lowercase query parameter' do + get(:index, params: { disable_pagination: 'true' }) + expect(JSON.parse(response.body)['links']).to be_nil + end + + it 'disabled pagination with an uppercase query parameter' do + get(:index, params: { disable_pagination: 'True' }) + expect(JSON.parse(response.body)['links']).to be_nil + end + end + end end end diff --git a/spec/controllers/v1/institutions_controller_spec.rb b/spec/controllers/v1/institutions_controller_spec.rb index 1f380907c..8d401afb0 100644 --- a/spec/controllers/v1/institutions_controller_spec.rb +++ b/spec/controllers/v1/institutions_controller_spec.rb @@ -385,6 +385,55 @@ def check_boolean_facets(facets) expect(response.media_type).to eq('application/json') expect(response).to match_response_schema('institution_search_results') end + + # tests for coordinate-based name+location search + it 'returns filtered results when searching by both name and coordinates' do + create(:institution, :production_version, :location, institution: 'HARVARD UNIVERSITY') + create(:institution, :production_version, :location, institution: 'BOSTON UNIVERSITY') + + get(:location, params: { + latitude: '32.7876', + longitude: '-79.9403', + distance: '50', + name: 'harvard' + }) + + results = JSON.parse(response.body)['data'] + expect(results.count).to eq(1) + expect(results[0]['attributes']['name']).to eq('HARVARD UNIVERSITY') + expect(response).to match_response_schema('institution_search_results') + end + + it 'returns no results when name does not match within location radius' do + create(:institution, :production_version, :location, institution: 'HARVARD UNIVERSITY') + + get(:location, params: { + latitude: '32.7876', + longitude: '-79.9403', + distance: '50', + name: 'stanford' + }) + + expect(JSON.parse(response.body)['data'].count).to eq(0) + expect(response).to match_response_schema('institution_search_results') + end + + it 'maintains accurate count in meta for coordinate and name search' do + create_list(:institution, 3, :production_version, :location) + create(:institution, :production_version, :location, institution: 'HARVARD UNIVERSITY') + + get(:location, params: { + latitude: '32.7876', + longitude: '-79.9403', + distance: '50', + name: 'harvard' + }) + + body = JSON.parse(response.body) + expect(body['data'].count).to eq(1) + expect(body['meta']['count']).to eq(1) + expect(response).to match_response_schema('institution_search_results') + end end context 'with compare results' do @@ -486,4 +535,33 @@ def check_boolean_facets(facets) expect(JSON.parse(response.body)['data'].count).to eq(0) end end + + context 'with program search results' do + before do + create(:version, :production) + create(:institution, :production_version, latitude: 30.1659, longitude: -93.2146) # Example institution + create(:institution_program, institution: Institution.last, description: 'Software Engineering') # Program associated with the institution + end + + it 'search returns results matching program description and location' do + get(:program, params: { description: 'Software Engineering', latitude: 30.1659, longitude: -93.2146 }) + expect(JSON.parse(response.body)['data'].count).to eq(1) # Expecting one result + expect(response.media_type).to eq('application/json') + expect(response).to match_response_schema('institution_search_results') + end + + it 'returns no results for non-matching program description' do + get(:program, params: { description: 'Non-existing Program', latitude: 30.1659, longitude: -93.2146 }) + expect(JSON.parse(response.body)['data'].count).to eq(0) # Expecting no results + expect(response.media_type).to eq('application/json') + expect(response).to match_response_schema('institution_search_results') + end + + it 'returns no results for non-matching location' do + get(:program, params: { description: 'Software Engineering', latitude: 0.0, longitude: 0.0 }) + expect(JSON.parse(response.body)['data'].count).to eq(0) # Expecting no results + expect(response.media_type).to eq('application/json') + expect(response).to match_response_schema('institution_search_results') + end + end end diff --git a/spec/factories/crosswalks.rb b/spec/factories/crosswalks.rb index d6fa6426c..cca5c1181 100644 --- a/spec/factories/crosswalks.rb +++ b/spec/factories/crosswalks.rb @@ -20,6 +20,22 @@ facility_code { '99Z99999' } end + # last 2 characters are less than 51 + trait :domestic_with_crosswalk_issue do + facility_code { '99Z99950' } + after(:create) do |crosswalk| + create(:crosswalk_issue, :partial_match_type, crosswalk: crosswalk) + end + end + + # last 2 characters are 51 or greater + trait :foreign_with_crosswalk_issue do + facility_code { '99Z99951' } + after(:create) do |crosswalk| + create(:crosswalk_issue, :partial_match_type, crosswalk: crosswalk) + end + end + trait :crosswalk_issue_matchable_by_cross do cross { '888888' } end diff --git a/spec/factories/ipeds_hds.rb b/spec/factories/ipeds_hds.rb index ffd9ea595..5e6903d71 100644 --- a/spec/factories/ipeds_hds.rb +++ b/spec/factories/ipeds_hds.rb @@ -27,6 +27,12 @@ zip { '94107' } end + trait :with_crosswalk_issue do + after(:create) do |ipeds_hd| + create(:crosswalk_issue, :partial_match_type, ipeds_hd: ipeds_hd) + end + end + initialize_with do new( cross: cross, vet_tuition_policy_url: vet_tuition_policy_url, diff --git a/spec/factories/weams.rb b/spec/factories/weams.rb index f10753a8b..3cbc23db4 100644 --- a/spec/factories/weams.rb +++ b/spec/factories/weams.rb @@ -178,6 +178,40 @@ facility_code { '99Z99999' } end + # last 2 characters are less than 51 + trait :domestic_with_crosswalk_issue do + facility_code { '99Z99950' } + after(:create) do |weam| + create(:crosswalk_issue, :partial_match_type, weam: weam) + end + end + + trait :domestic_with_ipeds_hd_crosswalk_issue do + facility_code { '99Z99950' } + cross { '888889' } + + after(:create) do |weam| + create(:ipeds_hd, :with_crosswalk_issue, cross: weam.cross) + end + end + + # last 2 characters are 51 or greater + trait :foreign_with_crosswalk_issue do + facility_code { '99Z99951' } + after(:create) do |weam| + create(:crosswalk_issue, :partial_match_type, weam: weam) + end + end + + trait :foreign_with_ipeds_hd_crosswalk_issue do + facility_code { '99Z99951' } + cross { '888890' } + + after(:create) do |weam| + create(:ipeds_hd, :with_crosswalk_issue, cross: weam.cross) + end + end + trait :arf_gi_bill do city { 'Test' } state { 'TN' } diff --git a/spec/models/crosswalk_issue_spec.rb b/spec/models/crosswalk_issue_spec.rb index 555449085..062ddd577 100644 --- a/spec/models/crosswalk_issue_spec.rb +++ b/spec/models/crosswalk_issue_spec.rb @@ -213,6 +213,46 @@ def ignore_and_validate_delete_of_only_partial_match_issue end end + describe '#by_domestic_crosswalks via crosswalk' do + it 'includes crosswalks that have facility codes with the last 2 characters < 51' do + create(:crosswalk, :domestic_with_crosswalk_issue) + create(:crosswalk, :foreign_with_crosswalk_issue) + + expect(described_class.by_domestic_crosswalks.count).to eq(1) + end + end + + describe '#by_domestic_crosswalks via weams' do + it 'includes crosswalks that have facility codes with the last 2 characters < 51' do + create(:weam, :domestic_with_crosswalk_issue) + create(:weam, :foreign_with_crosswalk_issue) + + expect(described_class.by_domestic_weams.count).to eq(1) + end + end + + describe '#by_domestic_crosswalks via ipeds_hds' do + it 'includes crosswalks that have facility codes with the last 2 characters < 51' do + create(:weam, :domestic_with_ipeds_hd_crosswalk_issue) + create(:weam, :foreign_with_ipeds_hd_crosswalk_issue) + + expect(described_class.by_domestic_iped_hds.count).to eq(1) + end + end + + describe '#domestic_partial_matches' do + it 'includes crosswalks that have facility codes with the last 2 characters < 51' do + create(:crosswalk, :domestic_with_crosswalk_issue) + create(:crosswalk, :foreign_with_crosswalk_issue) + create(:weam, :domestic_with_crosswalk_issue) + create(:weam, :foreign_with_crosswalk_issue) + create(:weam, :domestic_with_ipeds_hd_crosswalk_issue) + create(:weam, :foreign_with_ipeds_hd_crosswalk_issue) + + expect(described_class.domestic_partial_matches.count).to eq(3) + end + end + describe 'when building IPEDS orphans' do it 'excludes IpedsHD that match Crosswalk by cross (IPEDS)' do create :ipeds_hd, :crosswalk_issue_matchable_by_cross diff --git a/spec/models/institution_builder/institution_builder_run_add_data_3_spec.rb b/spec/models/institution_builder/institution_builder_run_add_data_3_spec.rb index 33cc9968c..fa6dd638d 100644 --- a/spec/models/institution_builder/institution_builder_run_add_data_3_spec.rb +++ b/spec/models/institution_builder/institution_builder_run_add_data_3_spec.rb @@ -180,61 +180,42 @@ end describe 'when generating institution programs' do - it 'properly generates institution programs from programs and edu_programs' do + it 'properly generates institution programs from programs' do create :program, facility_code: '1ZZZZZZZ' - create :edu_program, facility_code: '1ZZZZZZZ' expect { described_class.run(user) }.to change(InstitutionProgram, :count).from(0).to(1) expect(InstitutionProgram.first.institution_id).to eq(Institution.first.id) expect(Institution.first.version_id).to eq(Version.current_production.id) end - it 'does not generate duplicate institution programs for duplicate edu-programs' do - create :program, facility_code: '1ZZZZZZZ' - create :edu_program, facility_code: '1ZZZZZZZ' - create :edu_program, facility_code: '1ZZZZZZZ' - - expect { described_class.run(user) }.to change(InstitutionProgram, :count).from(0).to(1) - end + it 'generates unique institution programs for different programs belonging to the same institution' do + create :program, facility_code: '1ZZZZZZZ', description: 'COMPUTER SCIENCE 1' + create :program, facility_code: '1ZZZZZZZ', description: 'COMPUTER SCIENCE 2' - it 'does not generate duplicate institution programs for duplicate edu-programs with differently cased names' do - create :program, facility_code: '1ZZZZZZZ' - create :edu_program, facility_code: '1ZZZZZZZ', vet_tec_program: 'computer science' - create :edu_program, facility_code: '1ZZZZZZZ' - - expect { described_class.run(user) }.to change(InstitutionProgram, :count).from(0).to(1) + expect { described_class.run(user) }.to change(InstitutionProgram, :count).from(0).to(2) end it 'does not generate duplicate institution programs for duplicate programs' do - create :program, facility_code: '1ZZZZZZZ' - create :program, facility_code: '1ZZZZZZZ' - create :edu_program, facility_code: '1ZZZZZZZ' + create :program, facility_code: '1ZZZZZZZ', description: 'COMPUTER SCIENCE' + create :program, facility_code: '1ZZZZZZZ', description: 'COMPUTER SCIENCE' expect { described_class.run(user) }.to change(InstitutionProgram, :count).from(0).to(1) end it 'does not generate duplicate institution programs for duplicate programs with differently cased names' do + create :program, facility_code: '1ZZZZZZZ', description: 'COMPUTER SCIENCE' create :program, facility_code: '1ZZZZZZZ', description: 'computer science' - create :program, facility_code: '1ZZZZZZZ' - create :edu_program, facility_code: '1ZZZZZZZ' - - expect { described_class.run(user) }.to change(InstitutionProgram, :count).from(0).to(1) - end - - it 'defaults deduped InstitutionProgram length_in_weeks to 0' do - create :program, facility_code: '1ZZZZZZZ' - create :edu_program, facility_code: '1ZZZZZZZ' - create :edu_program, facility_code: '1ZZZZZZZ' expect { described_class.run(user) }.to change(InstitutionProgram, :count).from(0).to(1) - expect(InstitutionProgram.first.length_in_weeks).to eq(0) end - it 'does not generate institution programs without matching programs and edu_programs' do + # Join on edu_programs removed for time being + # Ensure edu_programs no longer necessary for institution_program generation + it 'generates institution programs without matching programs and edu_programs' do create :program, facility_code: '1ZZZZZZZ' create :edu_program, facility_code: '0001' described_class.run(user) - expect(InstitutionProgram.count).to eq(0) + expect(InstitutionProgram.count).to eq(1) end end diff --git a/spec/serializers/institution_program_serializer_spec.rb b/spec/serializers/institution_program_serializer_spec.rb index 54cc10a91..8987a73dc 100644 --- a/spec/serializers/institution_program_serializer_spec.rb +++ b/spec/serializers/institution_program_serializer_spec.rb @@ -21,14 +21,6 @@ expect(attributes['description']).to eq(institution_program.description) end - it 'includes length_in_hours' do - expect(attributes['length_in_hours']).to eq(institution_program.length_in_hours) - end - - it 'includes length_in_weeks' do - expect(attributes['length_in_weeks']).to eq(institution_program.length_in_weeks) - end - it 'includes facility_code' do expect(attributes['facility_code']).to eq(institution_program.facility_code) end @@ -53,10 +45,6 @@ expect(attributes['preferred_provider']).to eq(institution_program.preferred_provider) end - it 'includes tuition_amount' do - expect(attributes['tuition_amount']).to eq(institution_program.tuition_amount) - end - it 'includes va_bah' do expect(attributes['va_bah']).to eq(institution_program.va_bah) end diff --git a/spec/support/api/schemas/institution_profile.json b/spec/support/api/schemas/institution_profile.json index a50a9f5d5..66a72e845 100644 --- a/spec/support/api/schemas/institution_profile.json +++ b/spec/support/api/schemas/institution_profile.json @@ -478,6 +478,12 @@ "type": "object" } }, + "program_types": { + "type": ["null", "array"], + "items": { + "type": "string" + } + }, "section_103_message": { "type": ["null", "string"] }, diff --git a/spec/support/factory_generators.rb b/spec/support/factory_generators.rb index 0c419711a..19147abbb 100644 --- a/spec/support/factory_generators.rb +++ b/spec/support/factory_generators.rb @@ -2,7 +2,10 @@ FactoryBot.define do sequence(:facility_code) do |n| - n.to_s(32).rjust(8, '0').upcase + base32_part = n.to_s(32).rjust(6, '0').upcase + # default facility code to domestic. Last 2 digits are numeric and less than 51 + numeric_part = (n % 50).to_s.rjust(2, '0') + base32_part + numeric_part end sequence(:facility_code_ojt) do |n|