diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 650ca0ddc..cf5020eb5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -119,4 +119,14 @@ def initialize(code, message) errors.add(code, message) end end + + def convert_hyphen_to_dash + params.deep_transform_keys! { |key| key.tr("-", "_") } + end + + def formatted_errors(error) + error.record.errors.map do |attribute, errors| + errors.map { |error_message| {detail: "#{attribute} #{error_message}"} } + end.flatten + end end diff --git a/app/controllers/resources_controller.rb b/app/controllers/resources_controller.rb index f3ddb17d4..9b9763b7d 100644 --- a/app/controllers/resources_controller.rb +++ b/app/controllers/resources_controller.rb @@ -34,6 +34,30 @@ def push_to_onesky head :no_content end + def suggestions + resources_1 = ToolGroup + .matching_countries__negative_rule_false(params["country"]) + .matching_languages__negative_rule_false(params["languages"]) + .joins(:rule_countries, :rule_languages) + + resources_2 = ToolGroup + .countries_not_matching__negative_rule_true(params["country"]) + .matching_languages__negative_rule_false(params["languages"]) + .joins(:rule_countries, :rule_languages) + + resources_3 = ToolGroup + .matching_countries__negative_rule_false(params["country"]) + .languages_not_matching__negative_rule_true(params["languages"]) + .joins(:rule_countries, :rule_languages) + + resources_4 = ToolGroup + .countries_not_matching__negative_rule_true(params["country"]) + .matching_languages__negative_rule_false(params["languages"]) + .joins(:rule_countries, :rule_languages) + + render json: resources_1 + resources_2 + resources_3 + resources_4, status: :ok + end + private def cached_index_json diff --git a/app/controllers/rule_countries_controller.rb b/app/controllers/rule_countries_controller.rb new file mode 100644 index 000000000..12ddf44a6 --- /dev/null +++ b/app/controllers/rule_countries_controller.rb @@ -0,0 +1,36 @@ +class RuleCountriesController < ApplicationController + before_action :authorize! + + def create + create_rule_country + rescue ActiveRecord::RecordInvalid => e + render json: {errors: formatted_errors(e)}, status: :unprocessable_entity + end + + def update + update_rule_country + end + + def destroy + tool_group = ToolGroup.find(params[:tool_group_id]) + rule_country = tool_group.rule_countries.find(params[:id]) + rule_country.destroy! + head :no_content + end + + private + + def create_rule_country + tool_group = ToolGroup.find(params[:tool_group_id]) + created = tool_group.rule_countries.create!(permit_params(:tool_group_id, :negative_rule, countries: [])) + response.headers["Location"] = "tool_groups/#{created.id}" + render json: created, status: :created + end + + def update_rule_country + tool_group = ToolGroup.find(params[:tool_group_id]) + existing = tool_group.rule_countries.find(params[:id]) + existing.update!(permit_params(:negative_rule, countries: [])) + render json: existing, status: :accepted + end +end diff --git a/app/controllers/rule_languages_controller.rb b/app/controllers/rule_languages_controller.rb new file mode 100644 index 000000000..38253a558 --- /dev/null +++ b/app/controllers/rule_languages_controller.rb @@ -0,0 +1,37 @@ +class RuleLanguagesController < ApplicationController + before_action :authorize! + before_action :convert_hyphen_to_dash, only: [:create, :update] + + def create + create_rule_language + rescue ActiveRecord::RecordInvalid => e + render json: {errors: formatted_errors(e)}, status: :unprocessable_entity + end + + def update + update_rule_language + end + + def destroy + tool_group = ToolGroup.find(params[:tool_group_id]) + rule_language = tool_group.rule_languages.find(params[:id]) + rule_language.destroy! + head :no_content + end + + private + + def create_rule_language + tool_group = ToolGroup.find(params[:tool_group_id]) + created = tool_group.rule_languages.create!(permit_params(:tool_group_id, :negative_rule, languages: [])) + response.headers["Location"] = "tool_groups/#{created.id}" + render json: created, status: :created + end + + def update_rule_language + tool_group = ToolGroup.find(params[:tool_group_id]) + existing = tool_group.rule_languages.find(params[:id]) + existing.update!(permit_params(:negative_rule, languages: [])) + render json: existing, status: :accepted + end +end diff --git a/app/controllers/rule_praxes_controller.rb b/app/controllers/rule_praxes_controller.rb new file mode 100644 index 000000000..7619ee859 --- /dev/null +++ b/app/controllers/rule_praxes_controller.rb @@ -0,0 +1,36 @@ +class RulePraxesController < ApplicationController + before_action :authorize! + + def create + create_rule_praxis + rescue ActiveRecord::RecordInvalid => e + render json: {errors: formatted_errors(e)}, status: :unprocessable_entity + end + + def update + update_rule_praxis + end + + def destroy + tool_group = ToolGroup.find(params[:tool_group_id]) + rule_praxes = tool_group.rule_praxes.find(params[:id]) + rule_praxes.destroy! + head :no_content + end + + private + + def create_rule_praxis + tool_group = ToolGroup.find(params[:tool_group_id]) + created = tool_group.rule_praxes.create!(permit_params(:tool_group_id, :negative_rule, openness: [], confidence: [])) + response.headers["Location"] = "tool_groups/#{created.id}" + render json: created, status: :created + end + + def update_rule_praxis + tool_group = ToolGroup.find(params[:tool_group_id]) + existing = tool_group.rule_praxes.find(params[:id]) + existing.update!(permit_params(:negative_rule, openness: [], confidence: [])) + render json: existing, status: :accepted + end +end diff --git a/app/controllers/tool_groups_controller.rb b/app/controllers/tool_groups_controller.rb new file mode 100644 index 000000000..0da3ee542 --- /dev/null +++ b/app/controllers/tool_groups_controller.rb @@ -0,0 +1,79 @@ +class ToolGroupsController < ApplicationController + before_action :authorize! + before_action :convert_hyphen_to_dash, only: [:create, :update] + + def index + render json: tool_groups_ordered_by_name, include: params[:include], fields: field_params, status: :ok + end + + def create + create_tool_group + rescue ActiveRecord::RecordInvalid => e + render json: {errors: formatted_errors(e)}, status: :unprocessable_entity + end + + def create_tool + ResourceToolGroup.create!( + tool_group_id: params[:tool_group_id], + resource_id: params[:data][:attributes]["resource-id"], + suggestions_weight: params[:data][:attributes]["suggestions-weight"] + ) + + tool_group = ToolGroup.find(params[:tool_group_id]) + response.headers["Location"] = "tool_groups/#{tool_group.id}" + render json: tool_group, status: :created + rescue ActiveRecord::RecordInvalid => e + render json: {errors: formatted_errors(e)}, status: :unprocessable_entity + end + + def update_tool + existing = ResourceToolGroup.find(params[:id]) + existing.update!( + resource_id: params[:data][:attributes]["resource-id"], + suggestions_weight: params[:data][:attributes]["suggestions-weight"] + ) + render json: existing, status: :accepted + end + + def delete_tool + resource_tool_group = ResourceToolGroup.find(params[:id]) + resource_tool_group.destroy! + head :no_content + end + + def show + render json: load_tool_group, include: params[:include], fields: field_params, status: :ok + end + + def destroy + tool_group = ToolGroup.find(params[:id]) + tool_group.destroy! + head :no_content + end + + def update + update_tool_group + end + + private + + def tool_groups_ordered_by_name + ToolGroup.order(name: :asc) + end + + def create_tool_group + created = ToolGroup.create!(permit_params(:name, :suggestions_weight)) + response.headers["Location"] = "tool_groups/#{created.id}" + render json: created, status: :created + end + + def update_tool_group + existing = ToolGroup.find(params[:id]) + existing.update!(permit_params(:name, :suggestions_weight)) + render json: existing, status: :accepted + end + + def load_tool_group + ToolGroup.find(params[:id]) + end +end diff --git a/app/models/resource.rb b/app/models/resource.rb index 2833a7b67..a389369fc 100644 --- a/app/models/resource.rb +++ b/app/models/resource.rb @@ -12,6 +12,9 @@ class Resource < ActiveRecord::Base has_many :translated_pages has_many :translated_attributes has_many :custom_manifests + has_many :resource_tool_groups + has_many :tool_groups, through: :resource_tool_groups + belongs_to :metatool, optional: true, class_name: "Resource" belongs_to :default_variant, optional: true, class_name: "Resource" has_many :variants, class_name: "Resource", foreign_key: :metatool_id diff --git a/app/models/resource_tool_group.rb b/app/models/resource_tool_group.rb new file mode 100644 index 000000000..73836236d --- /dev/null +++ b/app/models/resource_tool_group.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# app/models/resource_tool_group.rb +class ResourceToolGroup < ApplicationRecord + belongs_to :resource + belongs_to :tool_group +end diff --git a/app/models/rule_country.rb b/app/models/rule_country.rb new file mode 100644 index 000000000..a3f0e8007 --- /dev/null +++ b/app/models/rule_country.rb @@ -0,0 +1,17 @@ +class RuleCountry < ApplicationRecord + belongs_to :tool_group + + validates :tool_group_id, uniqueness: {scope: [:countries, :negative_rule], message: "combination already exists"} + validate :validate_countries + + private + + def validate_countries + countries.each do |country| + unless country.match?(/\A[A-Z]{2}\z/) + errors.add(:countries, "must contain only ISO-3166 alpha-2 country codes") + break + end + end + end +end diff --git a/app/models/rule_language.rb b/app/models/rule_language.rb new file mode 100644 index 000000000..1cbeaba66 --- /dev/null +++ b/app/models/rule_language.rb @@ -0,0 +1,5 @@ +class RuleLanguage < ApplicationRecord + belongs_to :tool_group + + validates :tool_group_id, uniqueness: {scope: [:languages, :negative_rule], message: "combination already exists"} +end diff --git a/app/models/rule_praxis.rb b/app/models/rule_praxis.rb new file mode 100644 index 000000000..f9ca4cadf --- /dev/null +++ b/app/models/rule_praxis.rb @@ -0,0 +1,26 @@ +class RulePraxis < ApplicationRecord + self.table_name = "rule_praxes" + + belongs_to :tool_group + + validates :tool_group_id, uniqueness: {scope: [:openness, :confidence], message: "combination already exists"} + validate :validate_openness_or_confidence, if: :no_openness_or_confidence? + + validates_each :openness, :confidence do |record, attr, value| + next if value.blank? + + value.each do |v| + record.errors.add(attr, "must contain integer values between 1 and 5 or an empty array") unless v.is_a?(Integer) && (1..5).cover?(v) + end + end + + private + + def no_openness_or_confidence? + openness.blank? && confidence.blank? + end + + def validate_openness_or_confidence + errors.add(:base, "Either 'openness' or 'confidence' must be present") + end +end diff --git a/app/models/tool_group.rb b/app/models/tool_group.rb new file mode 100644 index 000000000..cd3861939 --- /dev/null +++ b/app/models/tool_group.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# ToolGroup model class +class ToolGroup < ApplicationRecord + validates :name, :suggestions_weight, presence: true + validates :name, uniqueness: true + + has_many :rule_languages, dependent: :destroy + has_many :rule_countries, dependent: :destroy + has_many :rule_praxes, class_name: "RulePraxis", dependent: :destroy + has_many :resource_tool_groups + has_many :resources, through: :resource_tool_groups + + scope :matching_countries__negative_rule_false, lambda { |country| + where("countries @> ARRAY[?]::varchar[] AND rule_countries.negative_rule = ?", country&.upcase, false) + } + + scope :matching_languages__negative_rule_false, lambda { |language| + where("languages && ARRAY[?]::varchar[] AND rule_languages.negative_rule = ?", language, false) + } + + scope :languages_not_matching__negative_rule_true, lambda { |languages| + where("NOT ?::varchar[] <@ languages", "{#{languages.join(',')}}") + .where("rule_languages.negative_rule = ?", true) + } + + scope :countries_not_matching__negative_rule_true, lambda { |country| + where.not("countries @> ARRAY[?]::varchar[]", country&.upcase) + .where("rule_countries.negative_rule = ?", true) + } +end diff --git a/app/serializers/resource_serializer.rb b/app/serializers/resource_serializer.rb index 62bc116b5..4d173074e 100644 --- a/app/serializers/resource_serializer.rb +++ b/app/serializers/resource_serializer.rb @@ -16,6 +16,10 @@ class ResourceSerializer < ActiveModel::Serializer has_many :custom_manifests, key: "custom-manifests" has_many :variants, if: -> { object&.resource_type&.name == "metatool" } has_many :translated_attributes, key: "translated-attributes" + + has_many :resource_tool_groups + has_many :tool_groups, through: :resource_tool_groups + belongs_to :default_variant, key: "default-variant", if: -> { object&.resource_type&.name == "metatool" } def attributes(*args) diff --git a/app/serializers/resource_tool_group_serializer.rb b/app/serializers/resource_tool_group_serializer.rb new file mode 100644 index 000000000..64ef6eaf1 --- /dev/null +++ b/app/serializers/resource_tool_group_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class ResourceToolGroupSerializer < ActiveModel::Serializer + attributes :resource_id, :tool_group + attribute :suggestions_weight, key: "suggestions-weight" + + type "tool-group-tool" + + belongs_to :resource + belongs_to :tool_group +end diff --git a/app/serializers/rule_country_serializer.rb b/app/serializers/rule_country_serializer.rb new file mode 100644 index 000000000..3014a3c1a --- /dev/null +++ b/app/serializers/rule_country_serializer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class RuleCountrySerializer < ActiveModel::Serializer + attributes :id, :countries + attribute :negative_rule, key: "negative-rule" + + type "tool-group-rule-country" + + belongs_to :tool_group +end diff --git a/app/serializers/rule_language_serializer.rb b/app/serializers/rule_language_serializer.rb new file mode 100644 index 000000000..aab432422 --- /dev/null +++ b/app/serializers/rule_language_serializer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class RuleLanguageSerializer < ActiveModel::Serializer + attributes :id, :languages + attribute :negative_rule, key: "negative-rule" + + type "tool-group-rule-language" + + belongs_to :tool_group +end diff --git a/app/serializers/rule_praxis_serializer.rb b/app/serializers/rule_praxis_serializer.rb new file mode 100644 index 000000000..3e16636f7 --- /dev/null +++ b/app/serializers/rule_praxis_serializer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class RulePraxisSerializer < ActiveModel::Serializer + attributes :id, :openness, :confidence + attribute :negative_rule, key: "negative-rule" + + type "tool-group-rule-praxis" + + belongs_to :tool_group +end diff --git a/app/serializers/tool_group_serializer.rb b/app/serializers/tool_group_serializer.rb new file mode 100644 index 000000000..c7d0b1d22 --- /dev/null +++ b/app/serializers/tool_group_serializer.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class ToolGroupSerializer < ActiveModel::Serializer + attributes :id, :name + attribute :suggestions_weight, key: "suggestions-weight" + + type "tool-group" + + has_many :rule_languages, key: "rules-language" + has_many :rule_countries, key: "rules-country" + has_many :rule_praxes, key: "rules-praxis" + has_many :resource_tool_groups, key: "resource-tool-groups" + has_many :resources, through: :resource_tool_groups + + def custom_rule_languages + object.rule_languages + end + + def custom_rule_countries + object.rule_countries + end + + def custom_rule_praxis + object.rule_praxes + end +end diff --git a/config/routes.rb b/config/routes.rb index 0cf7a0e69..c6bda858d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,6 +6,7 @@ resources :systems, only: [:index, :show] resources :languages resources :resource_types, only: [:index, :show] + get "resources/suggestions", to: "resources#suggestions" resources :resources do resources :languages, controller: :resource_languages, only: [:update, :show] @@ -30,6 +31,27 @@ resources :custom_manifests, only: [:create, :update, :destroy, :show] + resources :tool_groups, path: "tool-groups", only: [:create, :destroy, :index, :show, :update] do + post "tools", to: "tool_groups#create_tool" + put "tools/:id", to: "tool_groups#update_tool" + delete "tools/:id", to: "tool_groups#delete_tool" + end + + # Rule Languages + resources :tool_groups, path: "tool-groups", only: [] do + resources :rule_languages, path: "rules-language", only: [:create, :destroy, :update] + end + + # Rule Countries + resources :tool_groups, path: "tool-groups", only: [] do + resources :rule_countries, path: "rules-country", only: [:create, :destroy, :update] + end + + # Rule Praxis + resources :tool_groups, path: "tool-groups", only: [] do + resources :rule_praxes, path: "rules-praxis", only: [:create, :destroy, :update] + end + patch "user/counters/:id", to: "user_counters#update" # Legacy route for GodTools Android v5.7.0-v6.0.0 patch "user/me/counters/:id", to: "user_counters#update" # Legacy route for GodTools Android v6.0.1+ get "users/:user_id/counters", to: "user_counters#index" diff --git a/db/migrate/20230710210235_create_tool_groups.rb b/db/migrate/20230710210235_create_tool_groups.rb new file mode 100644 index 000000000..ec36dfbfd --- /dev/null +++ b/db/migrate/20230710210235_create_tool_groups.rb @@ -0,0 +1,10 @@ +class CreateToolGroups < ActiveRecord::Migration[6.1] + def change + create_table :tool_groups do |t| + t.string :name + t.float :suggestions_weight + + t.timestamps + end + end +end diff --git a/db/migrate/20230713233746_create_rule_languages.rb b/db/migrate/20230713233746_create_rule_languages.rb new file mode 100644 index 000000000..bced23532 --- /dev/null +++ b/db/migrate/20230713233746_create_rule_languages.rb @@ -0,0 +1,11 @@ +class CreateRuleLanguages < ActiveRecord::Migration[6.1] + def change + create_table :rule_languages do |t| + t.references :tool_group, null: false, foreign_key: true + t.string :languages, array: true, default: [] + t.boolean :negative_rule, default: false + + t.timestamps + end + end +end diff --git a/db/migrate/20230714185946_create_rule_countries.rb b/db/migrate/20230714185946_create_rule_countries.rb new file mode 100644 index 000000000..d7c43ce58 --- /dev/null +++ b/db/migrate/20230714185946_create_rule_countries.rb @@ -0,0 +1,11 @@ +class CreateRuleCountries < ActiveRecord::Migration[6.1] + def change + create_table :rule_countries do |t| + t.references :tool_group, null: false, foreign_key: true + t.string :countries, array: true, default: [] + t.boolean :negative_rule, default: false + + t.timestamps + end + end +end diff --git a/db/migrate/20230714202623_create_rule_praxis.rb b/db/migrate/20230714202623_create_rule_praxis.rb new file mode 100644 index 000000000..1b01c8896 --- /dev/null +++ b/db/migrate/20230714202623_create_rule_praxis.rb @@ -0,0 +1,12 @@ +class CreateRulePraxis < ActiveRecord::Migration[6.1] + def change + create_table :rule_praxis do |t| + t.references :tool_group, null: false, foreign_key: true + t.integer :openness, array: true, default: [] + t.integer :confidence, array: true, default: [] + t.boolean :negative_rule, default: false + + t.timestamps + end + end +end diff --git a/db/migrate/20230719201411_rename_rule_praxis_to_rule_praxes.rb b/db/migrate/20230719201411_rename_rule_praxis_to_rule_praxes.rb new file mode 100644 index 000000000..9d74eb7db --- /dev/null +++ b/db/migrate/20230719201411_rename_rule_praxis_to_rule_praxes.rb @@ -0,0 +1,5 @@ +class RenameRulePraxisToRulePraxes < ActiveRecord::Migration[6.1] + def change + rename_table :rule_praxis, :rule_praxes + end +end diff --git a/db/migrate/20230719224248_create_resource_tool_groups.rb b/db/migrate/20230719224248_create_resource_tool_groups.rb new file mode 100644 index 000000000..3da678e84 --- /dev/null +++ b/db/migrate/20230719224248_create_resource_tool_groups.rb @@ -0,0 +1,11 @@ +class CreateResourceToolGroups < ActiveRecord::Migration[6.1] + def change + create_table :resource_tool_groups do |t| + t.references :resource, null: false, foreign_key: true + t.references :tool_group, null: false, foreign_key: true + t.float :suggestions_weight + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index ac26cbbb7..952f90258 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_03_28_180034) do +ActiveRecord::Schema.define(version: 2023_07_19_224248) do # These are extensions that must be enabled in order to support this database enable_extension "citext" @@ -160,8 +160,6 @@ t.string "key", null: false t.string "value", null: false t.boolean "is_translatable", default: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.index ["key", "resource_id", "language_id"], name: "index_language_attributes_unique", unique: true t.index ["resource_id"], name: "index_language_attributes_on_resource_id" end @@ -182,6 +180,16 @@ t.index ["resource_id"], name: "index_pages_on_resource_id" end + create_table "resource_tool_groups", force: :cascade do |t| + t.bigint "resource_id", null: false + t.bigint "tool_group_id", null: false + t.float "suggestions_weight" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["resource_id"], name: "index_resource_tool_groups_on_resource_id" + t.index ["tool_group_id"], name: "index_resource_tool_groups_on_tool_group_id" + end + create_table "resource_types", id: :serial, force: :cascade do |t| t.string "name", null: false t.string "dtd_file", null: false @@ -208,6 +216,34 @@ t.index ["system_id"], name: "index_resources_on_system_id" end + create_table "rule_countries", force: :cascade do |t| + t.bigint "tool_group_id", null: false + t.string "countries", default: [], array: true + t.boolean "negative_rule", default: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["tool_group_id"], name: "index_rule_countries_on_tool_group_id" + end + + create_table "rule_languages", force: :cascade do |t| + t.bigint "tool_group_id", null: false + t.string "languages", default: [], array: true + t.boolean "negative_rule", default: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["tool_group_id"], name: "index_rule_languages_on_tool_group_id" + end + + create_table "rule_praxes", force: :cascade do |t| + t.bigint "tool_group_id", null: false + t.integer "openness", default: [], array: true + t.integer "confidence", default: [], array: true + t.boolean "negative_rule", default: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["tool_group_id"], name: "index_rule_praxes_on_tool_group_id" + end + create_table "systems", id: :serial, force: :cascade do |t| t.string "name", null: false t.index ["name"], name: "index_systems_on_name", unique: true @@ -222,6 +258,13 @@ t.index ["resource_id", "name"], name: "index_tips_on_resource_id_and_name", unique: true end + create_table "tool_groups", force: :cascade do |t| + t.string "name" + t.float "suggestions_weight" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + create_table "translated_attributes", force: :cascade do |t| t.integer "resource_id" t.string "key" @@ -268,7 +311,9 @@ t.date "last_decay", default: -> { "now()" } t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.string "values", default: [], array: true t.index ["user_id", "counter_name"], name: "index_user_counters_on_user_id_and_counter_name", unique: true + t.index ["values"], name: "index_user_counters_on_values", using: :gin end create_table "users", force: :cascade do |t| @@ -301,10 +346,15 @@ add_foreign_key "follow_ups", "destinations" add_foreign_key "follow_ups", "languages" add_foreign_key "pages", "resources" + add_foreign_key "resource_tool_groups", "resources" + add_foreign_key "resource_tool_groups", "tool_groups" add_foreign_key "resources", "resource_types" add_foreign_key "resources", "resources", column: "default_variant_id" add_foreign_key "resources", "resources", column: "metatool_id" add_foreign_key "resources", "systems" + add_foreign_key "rule_countries", "tool_groups" + add_foreign_key "rule_languages", "tool_groups" + add_foreign_key "rule_praxes", "tool_groups" add_foreign_key "translated_pages", "languages" add_foreign_key "translated_pages", "resources" add_foreign_key "translation_attributes", "translations" diff --git a/spec/acceptance/resources_controller_spec.rb b/spec/acceptance/resources_controller_spec.rb index 65a3e4f83..4a8ed2e6b 100644 --- a/spec/acceptance/resources_controller_spec.rb +++ b/spec/acceptance/resources_controller_spec.rb @@ -9,6 +9,152 @@ let(:raw_post) { params.to_json } let(:authorization) { AuthToken.generic_token } + let(:languages_fr_en) { ["fr", "en"] } + let(:languages_fr) { ["fr"] } + let(:languages_it) { ["it"] } + let(:languages_en) { ["en"] } + let(:countries_fr) { ["FR"] } + let(:countries_gb) { ["GB"] } + let(:countries_fr_us) { ["FR", "US"] } + let(:openness) { [1, 2, 3] } + let(:confidence) { [1, 2] } + + get "resources/suggestions" do + before(:each) do + FactoryBot.create(:tool_group, name: "one") + FactoryBot.create(:rule_country, tool_group: ToolGroup.first, countries: countries_fr_us) + FactoryBot.create(:rule_language, tool_group: ToolGroup.first, languages: languages_fr_en) + end + + # do_request languages: ["en", "es"], country: "fr", openness: "3" + + context "when matching country param contained in country rule with negative rule as false" do + before do + RuleCountry.first.update!(negative_rule: false) + RuleLanguage.first.update!(negative_rule: false) + end + + it "return coincidences" do + do_request country: "fr", languages: languages_fr + + expect(status).to be(200) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"].count).to eql 1 + end + + context "plus matching languages with negative rule as false" do + it "return coincidences" do + do_request country: "fr", languages: languages_fr_en + + expect(status).to be(200) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"].count).to eql 1 + end + end + + context "plus not matching languages with negative rule as false" do + before do + RuleLanguage.first.update!(languages: languages_fr_en) + end + + it "does not return coincidences" do + do_request country: "fr", languages: languages_it + + expect(status).to be(200) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"].count).to eql 0 + end + end + + context "plus not matching all languages with negative rule as true" do + before do + RuleLanguage.first.update!(languages: languages_en, negative_rule: true) + end + + it "return coincidences" do + do_request country: "fr", languages: languages_fr_en + + expect(status).to be(200) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"].count).to eql 1 + end + end + + context "plus not matching languages with negative rule as false" do + it "does not return coincidences" do + do_request country: "fr", languages: languages_it + + expect(status).to be(200) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"].count).to eql 0 + end + end + end + + context "when matching country param contained in country rule with negative rule as true" do + before do + RuleCountry.first.update!(negative_rule: true) + end + + it "does not return coincidences" do + do_request country: "fr", languages: languages_fr_en + + expect(status).to be(200) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"].count).to eql 0 + end + end + + context "when not matching country param contained in country rule with negative rule as false" do + before do + RuleCountry.first.update!(negative_rule: false) + end + + it "does not return coincidences" do + do_request country: "gb", languages: languages_fr_en + + expect(status).to be(200) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"].count).to eql 0 + end + end + + context "when not matching country param contained in country rule with negative rule as true" do + before do + RuleCountry.first.update!(negative_rule: true) + RuleLanguage.first.update!(negative_rule: false) + end + + context "plus matching languages with negative rule as true" do + before do + RuleLanguage.first.update!(negative_rule: true) + end + + it "does not return coincidences" do + do_request country: "gb", languages: languages_fr_en + + expect(status).to be(200) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"].count).to eql 0 + end + end + + context "plus matching languages with negative rule as false" do + before do + RuleLanguage.first.update!(negative_rule: false) + end + + it "return coincidences" do + do_request country: "gb", languages: languages_fr_en + + expect(status).to be(200) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"].count).to eql 1 + end + end + end + end + get "resources/" do it "get all resources" do do_request diff --git a/spec/acceptance/rule_countries_controller_spec.rb b/spec/acceptance/rule_countries_controller_spec.rb new file mode 100644 index 000000000..e14973458 --- /dev/null +++ b/spec/acceptance/rule_countries_controller_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "acceptance_helper" + +resource "RuleCountries" do + header "Accept", "application/vnd.api+json" + header "Content-Type", "application/vnd.api+json" + + let(:tool_group_id) { ToolGroup.first.id } + let(:raw_post) { params.to_json } + let(:authorization) { AuthToken.generic_token } + + before(:each) do + %i[one].each do |name| + FactoryBot.create(:tool_group, name: name) + end + FactoryBot.create(:rule_country, tool_group: ToolGroup.first) + end + + after(:each) do + RuleCountry.delete_all + ToolGroup.delete_all + end + + post "tool-groups/:id/rules-country" do + requires_authorization + + let(:valid_attrs) do + { + countries: ["CA", "FR", "US"], + negative_rule: "true" + } + end + + let(:invalid_attrs) do + { + countries: ["1A"], + negative_rule: "true" + } + end + + context "with valid countries values" do + it "creates a rule country" do + do_request tool_group_id: tool_group_id, data: {type: "tool-group-rules-country", attributes: valid_attrs} + + expect(status).to eq(201) + expect(JSON.parse(response_body)["data"]).not_to be_nil + end + end + + context "with invalid countries values" do + it "returns an error" do + do_request tool_group_id: tool_group_id, data: {type: "tool-group-rules-country", attributes: invalid_attrs} + + expect(status).to eq(400) + expect(JSON.parse(response_body)["data"]).to be_nil + expect(JSON.parse(response_body)["errors"][0]["detail"]).to eql "Validation failed: Countries must contain only ISO-3166 alpha-2 country codes" + end + end + end + + patch "tool-groups/:tool_group_id/rules-country/:id" do + requires_authorization + + let(:tool_group_id) { ToolGroup.first.id } + let(:id) { RuleCountry.first.id } + let(:countries) { ["FR", "AR"] } + + let(:attrs) do + { + countries: countries, + negative_rule: false + } + end + + it "update rule country" do + do_request data: {type: "tool-group-rules-country", attributes: attrs} + + expect(status).to be(202) + expect(JSON.parse(response_body)["data"]["attributes"]["countries"]).to eql countries + expect(JSON.parse(response_body)["data"]["attributes"]["negative-rule"]).to eql false + end + end + + delete "tool-groups/:tool_group_id/rules-country/:id" do + requires_authorization + + let(:tool_group_id) { ToolGroup.first.id } + let(:id) { RuleCountry.first.id } + + it "delete rule country" do + do_request + expect(status).to be(204) + end + end +end diff --git a/spec/acceptance/rule_languages_controller_spec.rb b/spec/acceptance/rule_languages_controller_spec.rb new file mode 100644 index 000000000..16d5ee25b --- /dev/null +++ b/spec/acceptance/rule_languages_controller_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "acceptance_helper" + +resource "RuleLanguages" do + header "Accept", "application/vnd.api+json" + header "Content-Type", "application/vnd.api+json" + + let(:raw_post) { params.to_json } + let(:authorization) { AuthToken.generic_token } + + before(:each) do + %i[one].each do |name| + FactoryBot.create(:tool_group, name: name) + end + FactoryBot.create(:rule_language, tool_group: ToolGroup.first) + end + + after(:each) do + RuleLanguage.delete_all + ToolGroup.delete_all + end + + post "tool-groups/:id/rules-language" do + requires_authorization + + let(:tool_group_id) { ToolGroup.first.id } + + let(:attrs) do + { + languages: ["en", "es"], + negative_rule: "true" + } + end + + it "create rule language" do + do_request tool_group_id: tool_group_id, data: {type: "tool-group-rules-language", attributes: attrs} + expect(status).to eq(201) + expect(JSON.parse(response_body)["data"]).not_to be_nil + end + end + + patch "tool-groups/:tool_group_id/rules-language/:id" do + requires_authorization + + let(:tool_group_id) { ToolGroup.first.id } + let(:id) { RuleLanguage.first.id } + let(:languages) { ["fr", "es", "pt"] } + + let(:attrs) do + { + languages: languages, + negative_rule: false + } + end + + it "update rule language" do + do_request data: {type: "tool-group-rules-language", attributes: attrs} + + expect(status).to be(202) + expect(JSON.parse(response_body)["data"]["attributes"]["languages"]).to eql languages + expect(JSON.parse(response_body)["data"]["attributes"]["negative-rule"]).to eql false + end + end + + delete "tool-groups/:tool_group_id/rules-language/:id" do + requires_authorization + + let(:tool_group_id) { ToolGroup.first.id } + let(:id) { RuleLanguage.first.id } + + it "delete rule language" do + do_request + expect(status).to be(204) + end + end +end diff --git a/spec/acceptance/rule_praxes_controller_spec.rb b/spec/acceptance/rule_praxes_controller_spec.rb new file mode 100644 index 000000000..73d842042 --- /dev/null +++ b/spec/acceptance/rule_praxes_controller_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require "acceptance_helper" + +resource "RulePraxes" do + header "Accept", "application/vnd.api+json" + header "Content-Type", "application/vnd.api+json" + + let(:raw_post) { params.to_json } + let(:authorization) { AuthToken.generic_token } + + before(:each) do + %i[one].each do |name| + FactoryBot.create(:tool_group, name: name) + end + FactoryBot.create(:rule_praxis, tool_group: ToolGroup.first) + end + + after(:each) do + RulePraxis.delete_all + ToolGroup.delete_all + end + + post "tool-groups/:id/rules-praxis" do + requires_authorization + let(:tool_group_id) { ToolGroup.first.id } + let(:openness) { [1, 2] } + let(:confidence) { [4, 5] } + + let(:valid_attrs) do + { + openness: openness, + confidence: confidence, + negative_rule: "true" + } + end + + let(:repeated_attrs) do + { + openness: openness, + confidence: confidence, + negative_rule: "true" + } + end + + let(:empty_attrs) do + { + openness: [], + confidence: [], + negative_rule: "true" + } + end + + let(:non_valid_openness_attr) do + { + openness: [0], + confidence: [1, 2], + negative_rule: "true" + } + end + + let(:non_valid_confidence_attr) do + { + openness: [1, 2, 3, 4, 5], + confidence: [6], + negative_rule: "true" + } + end + + context "with valid openness and confidence values" do + it "create rule praxis" do + do_request tool_group_id: tool_group_id, data: {type: "tool-group-rules-praxis", attributes: valid_attrs} + + expect(status).to eq(201) + expect(JSON.parse(response_body)["data"]).not_to be_nil + end + end + + context "with repeated openness and confidence values" do + before do + FactoryBot.create(:rule_praxis, tool_group_id: tool_group_id, openness: openness, confidence: confidence) + end + + it "returns an error" do + do_request tool_group_id: tool_group_id, data: {type: "tool-group-rules-praxis", attributes: repeated_attrs} + + expect(status).to eq(400) + expect(JSON.parse(response_body)["data"]).to be_nil + expect(JSON.parse(response_body)["errors"][0]["detail"]).to eql "Validation failed: Tool group combination already exists" + end + end + + context "with empty or null openness and confidence values" do + it "returns an error" do + do_request tool_group_id: tool_group_id, data: {type: "tool-group-rules-praxis", attributes: empty_attrs} + + expect(status).to eq(400) + expect(JSON.parse(response_body)["data"]).to be_nil + expect(JSON.parse(response_body)["errors"][0]["detail"]).to eql "Validation failed: Either 'openness' or 'confidence' must be present" + end + end + + context "with non valid openness values" do + it "returns an error" do + do_request tool_group_id: tool_group_id, data: {type: "tool-group-rules-praxis", attributes: non_valid_openness_attr} + + expect(status).to eq(400) + expect(JSON.parse(response_body)["data"]).to be_nil + expect(JSON.parse(response_body)["errors"][0]["detail"]).to eql "Validation failed: Openness must contain integer values between 1 and 5 or an empty array" + end + end + + context "with non valid confidence values" do + it "returns an error" do + do_request tool_group_id: tool_group_id, data: {type: "tool-group-rules-praxis", attributes: non_valid_confidence_attr} + + expect(status).to eq(400) + expect(JSON.parse(response_body)["data"]).to be_nil + expect(JSON.parse(response_body)["errors"][0]["detail"]).to eql "Validation failed: Confidence must contain integer values between 1 and 5 or an empty array" + end + end + end + + patch "tool-groups/:tool_group_id/rules-praxis/:id" do + requires_authorization + + let(:tool_group_id) { ToolGroup.first.id } + let(:id) { RulePraxis.first.id } + let(:openness) { [1, 2] } + let(:confidence) { [3, 4] } + + let(:attrs) do + { + openness: openness, + confidence: confidence, + negative_rule: false + } + end + + it "update rule praxis" do + do_request data: {type: "tool-group-rules-praxis", attributes: attrs} + + expect(status).to be(202) + expect(JSON.parse(response_body)["data"]["attributes"]["openness"]).to eql openness + expect(JSON.parse(response_body)["data"]["attributes"]["confidence"]).to eql confidence + expect(JSON.parse(response_body)["data"]["attributes"]["negative-rule"]).to eql false + end + end + + delete "tool-groups/:tool_group_id/rules-praxis/:id" do + requires_authorization + + let(:tool_group_id) { ToolGroup.first.id } + let(:id) { RulePraxis.first.id } + + it "delete rule praxis" do + do_request + expect(status).to be(204) + end + end +end diff --git a/spec/acceptance/tool_group_controller_spec.rb b/spec/acceptance/tool_group_controller_spec.rb new file mode 100644 index 000000000..04eeb9019 --- /dev/null +++ b/spec/acceptance/tool_group_controller_spec.rb @@ -0,0 +1,306 @@ +# frozen_string_literal: true + +require "acceptance_helper" + +resource "ToolGroups" do + header "Accept", "application/vnd.api+json" + header "Content-Type", "application/vnd.api+json" + + let(:raw_post) { params.to_json } + let(:authorization) { AuthToken.generic_token } + let(:languages) { ["es", "en"] } + let(:countries) { ["AR", "ES"] } + let(:openness) { [1, 2, 3] } + let(:confidence) { [1, 2] } + let(:metatool_resource_type) { ResourceType.find_by(name: "metatool") } + let(:resource) { FactoryBot.create(:resource, system_id: 1, resource_type: metatool_resource_type, name: "Resource Test One", abbreviation: "test1") } + let(:tool_group_first) { ToolGroup.first } + let(:tool_group_last) { ToolGroup.last } + + before(:each) do + %i[one two three].each do |name| + FactoryBot.create(:tool_group, name: name) + end + FactoryBot.create(:rule_language, tool_group: ToolGroup.first, languages: languages) + FactoryBot.create(:rule_country, tool_group: ToolGroup.first, countries: countries) + FactoryBot.create(:rule_praxis, tool_group: ToolGroup.first, openness: openness, confidence: confidence) + resource + end + + after(:each) do + RuleCountry.delete_all + RuleLanguage.delete_all + RulePraxis.delete_all + ResourceToolGroup.delete_all + ToolGroup.delete_all + end + + post "tool-groups" do + let(:attrs) do + { + name: "test", + suggestions_weight: "1.0" + } + end + + let(:attrs_invalid) do + { + name: "test", + suggestions_weight: "" + } + end + + requires_authorization + + it "create tool group" do + do_request data: {type: "tool-group", attributes: attrs} + expect(status).to eq(201) + expect(JSON.parse(response_body)["data"]).not_to be_nil + end + + it "returns error message when tool group is not created" do + do_request data: {type: "tool-group", attributes: attrs_invalid} + + expect(status).to eq(400) + expect(JSON.parse(response_body)["errors"]).not_to be_empty + expect(JSON.parse(response_body)["errors"][0]["detail"]).to eql "Validation failed: Suggestions weight can't be blank" + end + end + + post "tool-groups/:tool_group_id/tools" do + let(:attrs) do + { + "resource-id": Resource.first.id, + "suggestions-weight": "1.0" + } + end + + requires_authorization + + it "create tool group tool" do + do_request tool_group_id: tool_group_first.id, data: {type: "tool-group-tool", attributes: attrs} + expect(status).to eq(201) + expect(JSON.parse(response_body)["data"]).not_to be_nil + end + end + + put "tool-groups/:tool_group_id/tools/:id" do + requires_authorization + + let(:suggestions_weight) { 0.5 } + let(:id) { ResourceToolGroup.create(resource_id: resource.id, tool_group_id: tool_group_first.id, suggestions_weight: "1.0").id } + let(:attrs) do + { + "suggestions-weight": suggestions_weight, + "resource-id": resource.id + } + end + + it "update tool group tool" do + do_request id: id, data: {type: "tool-group-tool", attributes: attrs} + + expect(status).to eq(202) + expect(JSON.parse(response_body)["data"]).not_to be_nil + expect(JSON.parse(response_body)["data"]["attributes"]["suggestions-weight"]).to eql suggestions_weight + expect(JSON.parse(response_body)["data"]["attributes"]["resource-id"]).to eql resource.id + end + end + + delete "tool-groups/:tool_group_id/tools/:id" do + let(:attrs) do + { + tool_group_id: ToolGroup.first.id + } + end + + let(:id) { ResourceToolGroup.create(resource_id: resource.id, tool_group_id: tool_group_first.id, suggestions_weight: "1.0").id } + requires_authorization + + it "delete tool_group" do + do_request id: id, data: {type: "tool-group-tool", attributes: attrs} + + expect(status).to be(204) + end + end + + get "tool-groups" do + requires_authorization + + let(:include_all_rules) { "rules-language,rules-praxis,rules-country" } + let(:include_only_rules_language) { "rules-language" } + let(:include_only_rules_country) { "rules-country" } + let(:include_only_rules_praxis) { "rules-praxis" } + + context "including all rules related and all fields" do + it "list groups" do + do_request include: include_all_rules + + included = JSON.parse(response_body)["included"] + expect(status).to eq(200) + expect(JSON.parse(response_body)["data"].count).to eql 3 + expect(included.count).to eql 3 + + expect(included[0]["attributes"]["languages"]).to eql languages + expect(included[1]["attributes"]["countries"]).to eql countries + + expect(included[2]["attributes"]["openness"]).to eql openness + expect(included[2]["attributes"]["confidence"]).to eql confidence + end + end + + context "including for praxis only field openness" do + it "list groups" do + do_request include: include_only_rules_praxis, "fields[tool-group-rule-praxis]": "openness" + expect(status).to eq(200) + + included = JSON.parse(response_body)["included"] + expect(included[0]["attributes"].key?("openness")).to eql true + expect(included[0]["attributes"].key?("confidence")).to eql false + end + end + + context "including for praxis only field confidence" do + it "list groups" do + do_request include: include_only_rules_praxis, "fields[tool-group-rule-praxis]": "confidence" + + expect(status).to eq(200) + + included = JSON.parse(response_body)["included"] + expect(included[0]["attributes"].key?("openness")).to eql false + expect(included[0]["attributes"].key?("confidence")).to eql true + end + end + + context "including only rules language" do + it "list groups" do + do_request include: include_only_rules_language + expect(status).to eq(200) + + included = JSON.parse(response_body)["included"] + expect(included[0]["attributes"]["languages"]).to eql languages + expect(return_rules_included?(included, "type", "tool-group-rule-country")).to eql nil + expect(return_rules_included?(included, "type", "tool-group-rule-praxis")).to eql nil + end + end + + context "including only rules country" do + it "list groups" do + do_request include: include_only_rules_country + expect(status).to eq(200) + + included = JSON.parse(response_body)["included"] + expect(included[0]["attributes"]["countries"]).to eql countries + expect(return_rules_included?(included, "type", "tool-group-rule-language")).to eql nil + expect(return_rules_included?(included, "type", "tool-group-rule-praxis")).to eql nil + end + end + + context "including only rules praxis" do + it "list groups" do + do_request include: include_only_rules_praxis + expect(status).to eq(200) + + included = JSON.parse(response_body)["included"] + expect(included[0]["attributes"]["openness"]).to eql openness + expect(included[0]["attributes"]["confidence"]).to eql confidence + expect(return_rules_included?(included, "type", "tool-group-rule-language")).to eql nil + expect(return_rules_included?(included, "type", "tool-group-rule-country")).to eql nil + end + end + end + + get "tool-groups/:id" do + requires_authorization + let(:id) { ToolGroup.first.id } + let(:include_all_rules) { "rules-language,rules-praxis,rules-country" } + let(:include_only_rules_language) { "rules-language" } + let(:include_only_rules_country) { "rules-country" } + let(:include_only_rules_praxis) { "rules-praxis" } + + context "including all rules related" do + it "get tool_group by id" do + do_request id: id, include: include_all_rules + expect(status).to eq(200) + + included = JSON.parse(response_body)["included"] + expect(JSON.parse(response_body)["data"]["attributes"]["name"]).to eql "one" + expect(included[0]["attributes"]["languages"]).to eql languages + expect(included[1]["attributes"]["countries"]).to eql countries + expect(included[2]["attributes"]["openness"]).to eql openness + expect(included[2]["attributes"]["confidence"]).to eql confidence + end + end + + context "including only rules language" do + it "get tool_group by id" do + do_request id: id, include: include_only_rules_language + expect(status).to eq(200) + + included = JSON.parse(response_body)["included"] + expect(included[0]["attributes"]["languages"]).to eql languages + expect(return_rules_included?(included, "type", "tool-group-rule-country")).to eql nil + expect(return_rules_included?(included, "type", "tool-group-rule-praxis")).to eql nil + end + end + + context "including only rules country" do + it "get tool_group by id" do + do_request id: id, include: include_only_rules_country + expect(status).to eq(200) + + included = JSON.parse(response_body)["included"] + expect(included[0]["attributes"]["countries"]).to eql countries + expect(return_rules_included?(included, "type", "tool-group-rule-language")).to eql nil + expect(return_rules_included?(included, "type", "tool-group-rule-praxis")).to eql nil + end + end + + context "including only rules praxis" do + it "get tool_group by id" do + do_request id: id, include: include_only_rules_praxis + expect(status).to eq(200) + + included = JSON.parse(response_body)["included"] + expect(included[0]["attributes"]["openness"]).to eql openness + expect(included[0]["attributes"]["confidence"]).to eql confidence + expect(return_rules_included?(included, "type", "tool-group-rule-language")).to eql nil + expect(return_rules_included?(included, "type", "tool-group-rule-country")).to eql nil + end + end + end + + put "tool-groups/:id" do + requires_authorization + let(:id) { ToolGroup.first.id } + let(:attrs) do + { + name: "new name" + } + end + + it "update tool group" do + do_request data: {type: "tool-group", attributes: attrs} + + expect(status).to be(202) + expect(JSON.parse(response_body)["data"]["attributes"]["name"]).to eql "new name" + end + end + + delete "tool-groups/:id" do + let(:id) { ToolGroup.first.id } + + requires_authorization + + it "delete tool_group" do + do_request + + expect(status).to be(204) + end + end + + private + + def return_rules_included?(json_array, key_to_find, value_to_find) + value_to_find if json_array.any? { |json_element| json_element[key_to_find] == value_to_find } + end +end diff --git a/spec/factories/rule_countries.rb b/spec/factories/rule_countries.rb new file mode 100644 index 000000000..61addb331 --- /dev/null +++ b/spec/factories/rule_countries.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :rule_country do + tool_group_id { 1 } + negative_rule { true } + countries { ["BR"] } + end +end diff --git a/spec/factories/rule_languages.rb b/spec/factories/rule_languages.rb new file mode 100644 index 000000000..a62a8c9de --- /dev/null +++ b/spec/factories/rule_languages.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :rule_language do + tool_group_id { 1 } + negative_rule { true } + languages { ["en"] } + end +end diff --git a/spec/factories/rule_praxis.rb b/spec/factories/rule_praxis.rb new file mode 100644 index 000000000..a00326b4c --- /dev/null +++ b/spec/factories/rule_praxis.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :rule_praxis do + tool_group_id { 1 } + negative_rule { true } + openness { [1, 2, 3] } + confidence { [4, 5] } + end +end diff --git a/spec/factories/tool_groups.rb b/spec/factories/tool_groups.rb new file mode 100644 index 000000000..3ce505b8d --- /dev/null +++ b/spec/factories/tool_groups.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :tool_group do + name { "Group 1" } + suggestions_weight { 1.0 } + end +end diff --git a/spec/models/tool_group_spec.rb b/spec/models/tool_group_spec.rb new file mode 100644 index 000000000..fdbda2b1c --- /dev/null +++ b/spec/models/tool_group_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe ToolGroup, type: :model do + context "create new tool-group" do + subject { ToolGroup.new(name: "Group 1", suggestions_weight: 1.0) } + + it "is valid with a unique name" do + expect(subject).to be_valid + end + + it "is not valid with a duplicate name" do + FactoryBot.create(:tool_group) + attributes = {name: "Group 1", suggestions_weight: 1.0} + expect { described_class.create!(attributes) }.to raise_error(ActiveRecord::RecordInvalid) + expect(subject).not_to be_valid + expect(subject.errors[:name]).to include("has already been taken") + end + + it "validates creation with valid attributes" do + attributes = {name: "test", suggestions_weight: 1.0} + + expect do + result = described_class.create!(attributes) + expect(result.name).to eq("test") + expect(result.suggestions_weight).to eq(1.0) + end.to change(ToolGroup, :count).by(1) + end + + it "raises an error if the 'name' attribute does not exist" do + attributes = {name: nil, suggestions_weight: 1.0} + expect { described_class.create!(attributes) }.to raise_error(ActiveRecord::RecordInvalid) + end + + it "raises an error if the 'suggestions_weight' attribute does not exist" do + attributes = {name: "test", suggestions_weight: nil} + expect { described_class.create!(attributes) }.to raise_error(ActiveRecord::RecordInvalid) + end + end +end