diff --git a/.config/.codecov.yml b/.config/.codecov.yml new file mode 100644 index 000000000..9e14ade12 --- /dev/null +++ b/.config/.codecov.yml @@ -0,0 +1,5 @@ +# Ignore test files themselves in the Codecov report +# see: https://about.codecov.io/blog/should-i-include-test-files-in-code-coverage-calculations/ +ignore: + - "spec/" + - "*_spec.rb" diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 844014fb0..000000000 --- a/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -docker/developement/docker-compose.yml -docker/developement/Dockerfile -docker/run_tests/docker-compose.yml -docker/run_tests/Dockerfile -**/.git -LICENSE -README.md -mampf-gui-transparent.png -.dockerignore diff --git a/.github/workflows/docker-compose-cache.json b/.github/workflows/docker-compose-cache.json new file mode 100644 index 000000000..baee2cfd8 --- /dev/null +++ b/.github/workflows/docker-compose-cache.json @@ -0,0 +1,15 @@ +{ + "target": { + "mampf": { + "cache-from": [ + "type=registry,ref=ghcr.io/mampf-hd/mampf:cache" + ], + "cache-to": [ + "type=registry,ref=ghcr.io/mampf-hd/mampf:cache" + ], + "output": [ + "type=docker" + ] + } + } +} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..db47d5027 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,62 @@ +name: Testing + +on: + push: + branches: + - main + - dev + - experimental + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + +jobs: + # For the setup idea using Docker Buildx, see StackOverflow answer [1]. + # [2] and [3] might also be very useful. [4] is generally about Docker + # cache management within GitHub Actions. + # + # [1] https://stackoverflow.com/a/75544124/ + # [2] https://depot.dev/blog/docker-layer-caching-in-github-actions#docker-layer-caching-in-github-actions + # [3] https://www.deploysentinel.com/blog/docker-buildx-cache-with-github-actions + # [4] https://docs.docker.com/build/ci/github-actions/cache/ + unit-tests: + name: Unit tests + environment: testing + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + # see https://github.com/orgs/MaMpf-HD/packages?repo_name=mampf + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build docker images + # As the docker-compose.yml file uses contexts like "./../..", we have + # to change the working directory here. + working-directory: docker/test + run: | + docker buildx bake --file ./docker-compose.yml --file ./../../.github/workflows/docker-compose-cache.json + + - name: Run unit tests + working-directory: docker/test + run: | + docker compose run --entrypoint="" mampf sh -c "RAILS_ENV=test bundle exec rspec --format RSpec::Github::Formatter" + + - name: Report test coverage to codecov + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + files: ./coverage/coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + codecov_yml_path: ./config/codecov.yml diff --git a/.vscode/settings.json b/.vscode/settings.json index 473af2bd1..9dcca743d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -55,6 +55,16 @@ }, "rubyLsp.enableExperimentalFeatures": true, ////////////////////////////////////// + // Ruby Test Explorer + ////////////////////////////////////// + "rubyTestExplorer.testFramework": "rspec", + "rubyTestExplorer.rspecCommand": "python3 ./spec/rspec_inside_docker.py", + "rubyTestExplorer.rspecDirectory": "./spec/", + "rubyTestExplorer.logpanel": true, + "rubyTestExplorer.filePattern": [ + "*_spec.rb" + ], + ////////////////////////////////////// // Files ////////////////////////////////////// "files.exclude": { diff --git a/Gemfile b/Gemfile index 0e7992715..d300a1561 100644 --- a/Gemfile +++ b/Gemfile @@ -123,7 +123,7 @@ group :test do # Adds support for Capybara system testing and selenium driver gem "selenium-webdriver" # Easy installation and use of web drivers to run system tests with browsers - gem "database_cleaner" + gem "database_cleaner-active_record" gem "faker" gem "launchy" gem "simplecov", require: false @@ -138,6 +138,8 @@ group :test, :development, :docker_development do gem "cypress-on-rails", "~> 1.0" gem "simplecov-cobertura" + + gem "rspec-github" end gem "prometheus_exporter" diff --git a/Gemfile.lock b/Gemfile.lock index 651f9feff..bb7841bd3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -187,8 +187,6 @@ GEM cypress-on-rails (1.17.0) rack dalli (3.2.8) - database_cleaner (2.0.2) - database_cleaner-active_record (>= 2, < 3) database_cleaner-active_record (2.1.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) @@ -497,6 +495,8 @@ GEM rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) + rspec-github (2.4.0) + rspec-core (~> 3.0) rspec-mocks (3.13.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) @@ -686,7 +686,7 @@ DEPENDENCIES coveralls cypress-on-rails (~> 1.0) dalli (>= 2.7) - database_cleaner + database_cleaner-active_record devise devise-bootstrap-views erubis @@ -726,6 +726,7 @@ DEPENDENCIES responders rgl rqrcode + rspec-github rspec-rails rubocop (~> 1.63) rubocop-performance (~> 1.21) diff --git a/config/webpack/test.js b/config/webpack/test.js index 7f3342f96..b95b8458c 100644 --- a/config/webpack/test.js +++ b/config/webpack/test.js @@ -1,4 +1,4 @@ -process.env.NODE_ENV = process.env.NODE_ENV || "development"; +process.env.NODE_ENV = process.env.NODE_ENV || "test"; const environment = require("./environment"); diff --git a/docker/run_cypress_tests/Dockerfile b/docker/test/Dockerfile similarity index 92% rename from docker/run_cypress_tests/Dockerfile rename to docker/test/Dockerfile index f37415d1a..74cdc9b38 100644 --- a/docker/run_cypress_tests/Dockerfile +++ b/docker/test/Dockerfile @@ -66,5 +66,8 @@ RUN yarn install --production=false COPY --from=build-pdfcomprezzor /go/src/pdfcomprezzor.wasm /go/src/wasm_exec.js /usr/src/app/public/pdfcomprezzor/ COPY --from=build-pdfcomprezzor /go/src/pdfcomprezzor.wasm /go/src/wasm_exec.js / -COPY ./ /usr/src/app/ -RUN RAILS_ENV=test TEST_DATABASE_ADAPTER=postgresql rake assets:precompile +COPY . /usr/src/app/ +COPY ./docker/production/docker.env ./docker-dummy.env + +RUN set -o allexport && . ./docker-dummy.env && set +o allexport && \ + RAILS_ENV=test TEST_DATABASE_ADAPTER=nulldb bundle exec rails assets:precompile diff --git a/docker/run_cypress_tests/Dockerfile_cypress b/docker/test/Dockerfile_cypress similarity index 100% rename from docker/run_cypress_tests/Dockerfile_cypress rename to docker/test/Dockerfile_cypress diff --git a/docker/run_cypress_tests/docker-compose.local.yml b/docker/test/docker-compose.local.yml similarity index 93% rename from docker/run_cypress_tests/docker-compose.local.yml rename to docker/test/docker-compose.local.yml index 83c57390a..1b151338c 100644 --- a/docker/run_cypress_tests/docker-compose.local.yml +++ b/docker/test/docker-compose.local.yml @@ -30,12 +30,12 @@ services: mampf: build: context: ./../.. - dockerfile: docker/run_cypress_tests/Dockerfile + dockerfile: docker/test/Dockerfile image: mampf:tests ports: - "127.0.0.1:3000:3000" # TODO: Use this - # entrypoint: /usr/src/app/docker/run_cypress_tests/run_tests.sh + # entrypoint: /usr/src/app/docker/test/run_tests.sh entrypoint: ./entrypoint.sh environment: RAILS_ENV: test @@ -76,7 +76,7 @@ services: image: mampf-cypress build: context: ./../.. - dockerfile: docker/run_cypress_tests/Dockerfile_cypress + dockerfile: docker/test/Dockerfile_cypress environment: CYPRESS_baseUrl: http://mampf:3000 entrypoint: bash -c "while ! curl -s $$CYPRESS_baseUrl > /dev/null; do echo waiting for MaMpf to come online at $$CYPRESS_baseUrl; sleep 1; done; npx cypress run $$@" diff --git a/docker/run_cypress_tests/docker-compose.yml b/docker/test/docker-compose.yml similarity index 70% rename from docker/run_cypress_tests/docker-compose.yml rename to docker/test/docker-compose.yml index e4a8d5139..23e00fb3c 100644 --- a/docker/run_cypress_tests/docker-compose.yml +++ b/docker/test/docker-compose.yml @@ -33,13 +33,14 @@ services: image: dockage/mailcatcher:latest networks: - backend + mampf: build: context: ./../.. - dockerfile: docker/run_cypress_tests/Dockerfile + dockerfile: docker/test/Dockerfile image: mampf:tests # TODO: Use this - # entrypoint: /usr/src/app/docker/run_cypress_tests/run_tests.sh + # entrypoint: /usr/src/app/docker/test/run_tests.sh entrypoint: /usr/src/app/entrypoint.sh environment: RAILS_ENV: test @@ -58,14 +59,16 @@ services: ERDBEERE_API: https://erdbeere.mathi.uni-heidelberg.de/api/v1 MUESLI_SERVER: https://muesli.mathi.uni-heidelberg.de PROJECT_EMAIL: project@localhost - FEEBACK_EMAIL: feedback@localhost + FEEDBACK_EMAIL: feedback@localhost PROJECT_NOTIFICATION_EMAIL: project+notification@localhost + ERROR_EMAIL: mampf-error@localhost MEDIA_FOLDER: mampf REDIS_URL: redis://redis:6379/1 SOLR_HOST: solr SOLR_PORT: 8983 SOLR_PATH: /solr/test SPROCKETS_CACHE: /cache + BLOG: https://mampf.blog volumes: - type: bind source: ../../spec/ @@ -79,23 +82,3 @@ services: networks: - backend - frontend - - cypress_runner: - image: mampf-cypress - build: - context: ./../.. - dockerfile: docker/run_cypress_tests/Dockerfile_cypress - environment: - CYPRESS_baseUrl: http://mampf:3000 - volumes: - - ../../spec/cypress/e2e:/cypress/e2e:ro - - ../../spec/cypress/fixtures:/cypress/fixtures:ro - - ../../spec/cypress.config.js:/cypress.config.js:ro - - ../../.git:/.git:ro - # cypress outputs are saved here (needed only locally) - #- ../../cypress/videos:/cypress/videos - #- ../../cypress/screenshots:/cypress/screenshots - depends_on: - - mampf - networks: - - frontend diff --git a/docker/run_cypress_tests/run_tests.sh b/docker/test/run_tests.sh similarity index 100% rename from docker/run_cypress_tests/run_tests.sh rename to docker/test/run_tests.sh diff --git a/spec/factories/users.rb b/spec/factories/users.rb index c4b8dbbc7..b9affdd33 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -35,5 +35,11 @@ factory :confirmed_user, traits: [:skip_confirmation_notification, :auto_confirmed, :consented] + + factory :confirmed_user_en, traits: [:skip_confirmation_notification, + :auto_confirmed, + :consented] do + locale { "en" } + end end end diff --git a/spec/models/announcement_spec.rb b/spec/models/announcement_spec.rb index 7d5112ae3..a021f9997 100644 --- a/spec/models/announcement_spec.rb +++ b/spec/models/announcement_spec.rb @@ -16,7 +16,7 @@ # test traits describe "with lecture" do - before :all do + before :each do @announcement = FactoryBot.build(:announcement, :with_lecture) end it "has a valid factory" do diff --git a/spec/models/answer_spec.rb b/spec/models/answer_spec.rb index b8211e4e5..7342306bf 100644 --- a/spec/models/answer_spec.rb +++ b/spec/models/answer_spec.rb @@ -15,7 +15,7 @@ # test traits describe "with stuff" do - before :all do + before :each do @answer = FactoryBot.build(:answer, :with_stuff) end it "has a text" do diff --git a/spec/models/chapter_spec.rb b/spec/models/chapter_spec.rb index af3a1a262..4630bf445 100644 --- a/spec/models/chapter_spec.rb +++ b/spec/models/chapter_spec.rb @@ -15,7 +15,7 @@ # test traits describe "chapter with sections" do - before :all do + before :each do @chapter = FactoryBot.build(:chapter, :with_sections) end it "has a valid factory" do diff --git a/spec/models/consumption_spec.rb b/spec/models/consumption_spec.rb index 698776175..9b80fe8f3 100644 --- a/spec/models/consumption_spec.rb +++ b/spec/models/consumption_spec.rb @@ -8,7 +8,7 @@ # describe traits describe "with stuff" do - before :all do + before :each do @consumption = FactoryBot.build(:consumption, :with_stuff) end it "has a medium id" do diff --git a/spec/models/course_spec.rb b/spec/models/course_spec.rb index 46165b833..353fe81db 100644 --- a/spec/models/course_spec.rb +++ b/spec/models/course_spec.rb @@ -29,7 +29,7 @@ # Test traits describe "course with tags" do - before :all do + before :each do @course = FactoryBot.build(:course, :with_tags) end it "has a valid factory" do @@ -98,7 +98,7 @@ # test callbacks describe "after save" do - before :all do + before :each do @course = FactoryBot.create(:course) @lecture = FactoryBot.create(:lecture_with_sparse_toc, course: @course) @lesson = FactoryBot.create(:valid_lesson, lecture: @lecture) @@ -283,7 +283,7 @@ end context "subscribable lectures" do - before :all do + before :each do @admin = FactoryBot.create(:confirmed_user, admin: true) @course_editor = FactoryBot.create(:confirmed_user) @editor = FactoryBot.create(:confirmed_user) @@ -334,7 +334,7 @@ end context "lecture sorting" do - before :all do + before :each do @course = FactoryBot.create(:course) year = Faker::Number.between(from: 1_000_001, to: 100_000_000) term1 = FactoryBot.create(:term, year: year, season: "SS") @@ -383,7 +383,7 @@ end context "lecture subscriptions" do - before :all do + before :each do @course = FactoryBot.create(:course) year = Faker::Number.between(from: 1_000_001, to: 100_000_000) term1 = FactoryBot.create(:term, year: year, season: "SS") @@ -436,7 +436,7 @@ end describe "#edited_by?" do - before :all do + before :each do @user = FactoryBot.create(:confirmed_user) end @@ -495,7 +495,7 @@ end describe "#removable_by?" do - before :all do + before :each do @user = FactoryBot.create(:confirmed_user) end @@ -511,7 +511,7 @@ end context "media and their items with inheritance" do - before :all do + before :each do @course = FactoryBot.create(:course, short_title: "LA2") term = FactoryBot.create(:term, year: 2020, season: "SS") lecture = FactoryBot.create(:lecture_with_sparse_toc, course: @course, @@ -576,7 +576,7 @@ end context "class methods for select forms" do - before :all do + before :each do Course.destroy_all @user = FactoryBot.create(:confirmed_user) @admin = FactoryBot.create(:confirmed_user, admin: true) @@ -644,7 +644,7 @@ end context "complex question methods" do - before :all do + before :each do @course = FactoryBot.create(:course) @lecture1 = FactoryBot.create(:lecture, :released_for_all, course: @course) @@ -812,7 +812,7 @@ end context "image methods" do - before :all do + before :each do @course = FactoryBot.create(:course, :with_image) end @@ -884,7 +884,7 @@ end describe "self.similar_courses" do - before :all do + before :each do Course.destroy_all @course1 = FactoryBot.create(:course, title: "Algebra 1") @course2 = FactoryBot.create(:course, title: "Algebra 2") @@ -908,7 +908,7 @@ end describe "#search_by" do - before :all do + before :each do Course.destroy_all @editor1 = FactoryBot.create(:confirmed_user) @editor2 = FactoryBot.create(:confirmed_user) diff --git a/spec/models/interaction_spec.rb b/spec/models/interaction_spec.rb index d42264c37..3e355b1e2 100644 --- a/spec/models/interaction_spec.rb +++ b/spec/models/interaction_spec.rb @@ -8,7 +8,7 @@ # describe traits describe "with stuff" do - before :all do + before :each do @interaction = FactoryBot.build(:interaction, :with_stuff) end it "has a session id" do diff --git a/spec/models/lecture_spec.rb b/spec/models/lecture_spec.rb index 26d67fbdb..d2ba05682 100644 --- a/spec/models/lecture_spec.rb +++ b/spec/models/lecture_spec.rb @@ -28,7 +28,7 @@ # Test traits describe "lecture with organizational stuff" do - before :all do + before :each do @lecture = FactoryBot.build(:lecture, :with_organizational_stuff) end it "has a valid factory" do @@ -42,7 +42,7 @@ end end describe "lecture which is released for all" do - before :all do + before :each do @lecture = FactoryBot.build(:lecture, :released_for_all) end it "has a valid factory" do @@ -53,7 +53,7 @@ end end describe "term independent lecture" do - before :all do + before :each do @lecture = FactoryBot.build(:lecture, :term_independent) end it "has a valid factory" do @@ -64,7 +64,7 @@ end end describe "with table of contents" do - before :all do + before :each do @lecture = FactoryBot.build(:lecture, :with_toc) end it "has 3 chapters" do @@ -75,7 +75,7 @@ end end describe "with sparse table of contents" do - before :all do + before :each do @lecture = FactoryBot.build(:lecture, :with_sparse_toc) end it "has one chapter" do diff --git a/spec/models/medium_publisher_spec.rb b/spec/models/medium_publisher_spec.rb index 62f3665ae..1b5f4ae98 100644 --- a/spec/models/medium_publisher_spec.rb +++ b/spec/models/medium_publisher_spec.rb @@ -13,7 +13,7 @@ end describe "::parse" do - before :all do + before :each do @medium = FactoryBot.create(:lecture_medium) @user = FactoryBot.create(:confirmed_user) end @@ -79,7 +79,7 @@ describe "#publish!" do context "without extra stuff" do - before :all do + before :each do @medium = FactoryBot.create(:lecture_medium) lecture = @medium.teachable lecture.update(released: "all") @@ -160,7 +160,7 @@ end describe "#assignment" do - before :all do + before :each do @medium = FactoryBot.create(:lecture_medium) @lecture = @medium.teachable user = FactoryBot.create(:confirmed_user) @@ -192,7 +192,7 @@ end describe "#errors" do - before :all do + before :each do @medium = FactoryBot.create(:lecture_medium) @user = FactoryBot.create(:confirmed_user) end diff --git a/spec/models/medium_spec.rb b/spec/models/medium_spec.rb index 2fca9e938..371cb2d80 100644 --- a/spec/models/medium_spec.rb +++ b/spec/models/medium_spec.rb @@ -95,7 +95,7 @@ end describe "lesson medium" do - before :all do + before :each do @medium = FactoryBot.build(:lesson_medium) end it "has a valid factory" do @@ -110,7 +110,7 @@ end describe "lecture medium" do - before :all do + before :each do @medium = FactoryBot.build(:lecture_medium) end it "has a valid factory" do @@ -125,7 +125,7 @@ end describe "course medium" do - before :all do + before :each do @medium = FactoryBot.build(:course_medium) end it "has a valid factory" do diff --git a/spec/models/notion_spec.rb b/spec/models/notion_spec.rb index 31c6fa334..4d79da4a9 100644 --- a/spec/models/notion_spec.rb +++ b/spec/models/notion_spec.rb @@ -24,7 +24,7 @@ # test traits describe "notion with tag" do - before :all do + before :each do @notion = FactoryBot.build(:notion, :with_tag) end it "has a valid factory" do diff --git a/spec/models/probe_spec.rb b/spec/models/probe_spec.rb index 59a567d97..e0f7bfa10 100644 --- a/spec/models/probe_spec.rb +++ b/spec/models/probe_spec.rb @@ -8,7 +8,7 @@ # describe traits describe "with stuff" do - before :all do + before :each do @probe = FactoryBot.build(:probe, :with_stuff) end it "has a question id" do diff --git a/spec/models/question_sampler_spec.rb b/spec/models/question_sampler_spec.rb index 51ad1425f..4c1a3e820 100644 --- a/spec/models/question_sampler_spec.rb +++ b/spec/models/question_sampler_spec.rb @@ -1,10 +1,12 @@ +require "rails_helper" + RSpec.describe(QuestionSampler, type: :model) do it "has a valid factory" do expect(FactoryBot.build(:question_sampler)).to be_kind_of(QuestionSampler) end describe "#sample!" do - before :all do + before :each do @questions = Question.where(id: FactoryBot.create_list(:valid_question, 4) .map(&:id)) end @@ -28,7 +30,7 @@ end context "in a simple example" do - before :all do + before :each do tags = Tag.where(id: FactoryBot.create_list(:tag, 3).map(&:id)) @questions[0].tags << tags[0] @questions[1].tags << tags[1] diff --git a/spec/models/question_spec.rb b/spec/models/question_spec.rb index b29054ea5..58332c854 100644 --- a/spec/models/question_spec.rb +++ b/spec/models/question_spec.rb @@ -10,7 +10,7 @@ # test traits describe "with stuff" do - before :all do + before :each do @question = FactoryBot.build(:question, :with_stuff) end it "has a text" do diff --git a/spec/models/quiz_certificate_spec.rb b/spec/models/quiz_certificate_spec.rb index 9780e1d6d..631b6e871 100644 --- a/spec/models/quiz_certificate_spec.rb +++ b/spec/models/quiz_certificate_spec.rb @@ -1,12 +1,12 @@ require "rails_helper" RSpec.describe(QuizCertificate, type: :model) do + # test validations - this is done on the level of the parent Medium model + it "has a valid factory" do expect(FactoryBot.build(:quiz_certificate)).to be_valid end - # test validations - this is done one the level of the parent Medium model - it "is invalid without a quiz" do expect(FactoryBot.build(:quiz_certificate, quiz: nil)).to be_invalid end diff --git a/spec/models/quiz_graph_spec.rb b/spec/models/quiz_graph_spec.rb index 8f1d970a1..c86c00deb 100644 --- a/spec/models/quiz_graph_spec.rb +++ b/spec/models/quiz_graph_spec.rb @@ -8,7 +8,7 @@ # test traits and subfactories describe "linear graph" do - before :all do + before :each do @graph = FactoryBot.build(:quiz_graph, :linear) end it "does not contain errors" do diff --git a/spec/models/quiz_spec.rb b/spec/models/quiz_spec.rb index 1e35bc7dd..e0aa378f8 100644 --- a/spec/models/quiz_spec.rb +++ b/spec/models/quiz_spec.rb @@ -10,7 +10,7 @@ # test traits and subfactories describe "with quiz graph" do - before :all do + before :each do @quiz = FactoryBot.build(:valid_quiz, :with_quiz_graph) end it "has no errors" do diff --git a/spec/models/referral_spec.rb b/spec/models/referral_spec.rb index 200521a9c..a1dcc4fa7 100644 --- a/spec/models/referral_spec.rb +++ b/spec/models/referral_spec.rb @@ -18,7 +18,7 @@ # test traits and subfactories describe "with times" do - before :all do + before :each do @referral = FactoryBot.build(:referral, :with_times) end it "has a valid factory" do diff --git a/spec/models/submission_cleaner_spec.rb b/spec/models/submission_cleaner_spec.rb index a1670c666..b6ec9a260 100644 --- a/spec/models/submission_cleaner_spec.rb +++ b/spec/models/submission_cleaner_spec.rb @@ -7,7 +7,7 @@ end describe "with sample submissions" do - before :all do + before :each do Term.destroy_all ActionMailer::Base.deliveries = [] @term1 = FactoryBot.create(:term, year: Time.zone.today.year, season: "SS") diff --git a/spec/models/talk_spec.rb b/spec/models/talk_spec.rb index a18242241..d8750bd31 100644 --- a/spec/models/talk_spec.rb +++ b/spec/models/talk_spec.rb @@ -60,7 +60,7 @@ end context "title methods" do - before :all do + before :each do I18n.with_locale(:de) do course = FactoryBot.build(:course, title: "Algebra 1", short_title: "Alg1") @@ -132,7 +132,7 @@ end context "locale methods" do - before :all do + before :each do I18n.with_locale(:de) do course = FactoryBot.build(:course, title: "Algebra 1", short_title: "Alg1") @@ -159,7 +159,7 @@ end context "position methods" do - before :all do + before :each do lecture = FactoryBot.build(:lecture) @talk1 = FactoryBot.create(:talk, lecture: lecture) @talk2 = FactoryBot.create(:talk, lecture: lecture) diff --git a/spec/models/talk_tag_join_spec.rb b/spec/models/talk_tag_join_spec.rb deleted file mode 100644 index 1b5e7edeb..000000000 --- a/spec/models/talk_tag_join_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "rails_helper" - -RSpec.describe(TalkTagJoin, type: :model) do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/teachable_parser_spec.rb b/spec/models/teachable_parser_spec.rb index 7068fb2ba..d021b5c17 100644 --- a/spec/models/teachable_parser_spec.rb +++ b/spec/models/teachable_parser_spec.rb @@ -4,7 +4,7 @@ end describe "#teachables_as_strings" do - before :all do + before :each do DatabaseCleaner.clean course1 = FactoryBot.create(:course) lecture1 = FactoryBot.create(:lecture, course: course1) diff --git a/spec/models/term_spec.rb b/spec/models/term_spec.rb index 5806d9eb6..1eca3529b 100644 --- a/spec/models/term_spec.rb +++ b/spec/models/term_spec.rb @@ -40,7 +40,7 @@ # test traits describe "summer term" do - before :all do + before :each do @term = FactoryBot.build(:term, :summer) end it "has a valid factory" do diff --git a/spec/models/user_cleaner_spec.rb b/spec/models/user_cleaner_spec.rb index 4549cfd4c..d33724a05 100644 --- a/spec/models/user_cleaner_spec.rb +++ b/spec/models/user_cleaner_spec.rb @@ -5,12 +5,15 @@ expect(FactoryBot.build(:user_cleaner)) .to be_kind_of(UserCleaner) end - - it "can destroy users" do - n_users = User.all.size - u = FactoryBot.build(:user_cleaner, :with_hashed_user) - expect(User.all.size).to eq(n_users + 1) - u.delete_ghosts - expect(User.all.size).to eq(n_users) - end + # Right now, delete_ghosts is commented out in user_cleaner.rb + # as we want to avoid accidental deletion of users in production. + # We will come up with a new strategy for cleaning up old users. + # + # it "can destroy users" do + # n_users = User.all.size + # u = FactoryBot.build(:user_cleaner, :with_hashed_user) + # expect(User.all.size).to eq(n_users + 1) + # u.delete_ghosts + # expect(User.all.size).to eq(n_users) + # end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index cc5dd6d5f..6675cbb53 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -34,7 +34,7 @@ end describe "user with subscribed lectures" do - before :all do + before :each do @user = FactoryBot.build(:user, :with_lectures) end it "has a valid factory" do @@ -70,7 +70,7 @@ # test methods - NEEDS TO BE REFACTORED # describe '#related_lectures' do - # before :all do + # before :each do # @preceding_course = FactoryBot.create(:course) # @course = FactoryBot.create(:course) # @lecture = FactoryBot.create(:lecture, course: @course) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 1c4c94a3f..202207bd1 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -34,10 +34,14 @@ # For Devise >= 4.1.0 config.include Devise::Test::ControllerHelpers, type: :controller + # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. + # We set it to false here since we are using the DatabaseCleaner gem instead. + # Also see https://avdi.codes/configuring-database_cleaner-with-rails-rspec-capybara-and-selenium/ config.use_transactional_fixtures = false + # RSpec Rails can automatically mix in different behaviours to your tests # based on their file location, for example enabling you to call `get` and # `post` in specs under `spec/controllers`. diff --git a/spec/requests/media_spec.rb b/spec/requests/media_spec.rb index 5f9a62277..3aa2ec5fb 100644 --- a/spec/requests/media_spec.rb +++ b/spec/requests/media_spec.rb @@ -1,10 +1,20 @@ require "rails_helper" +NO_HITS_MSG = "The search has not returned any hits".freeze + +def expect_no_results(response) + expect(response.body).to include(NO_HITS_MSG) +end + +def expect_all_results(response) + expect(response.body).not_to include(NO_HITS_MSG) + num_hits = parse_media_search(response) + expect(num_hits).to eq(Medium.where(released: "all").size) +end + RSpec.describe("Media", type: :request) do describe "#search_by" do before do - Medium.destroy_all - @medium1 = FactoryBot.create(:medium, :with_teachable, :with_editors, :released, sort: "Nuesse", description: "Erstes Medium") @medium2 = FactoryBot.create(:medium, :with_teachable, :with_editors, :released, @@ -16,6 +26,11 @@ @medium5 = FactoryBot.create(:medium, :with_teachable, :with_editors, :released, :with_tags, sort: "Nuesse", description: "Anderes Medium") + @tag1 = FactoryBot.create(:tag, title: "mampf adventures") + @tag2 = FactoryBot.create(:tag, title: "topology") + @medium4.tags << @tag1 + @medium5.tags << @tag2 + @lecture1 = FactoryBot.create(:lecture) @medium6 = FactoryBot.create(:medium, :with_teachable, :with_editors, :released, teachable: @lecture1, sort: "Nuesse", @@ -27,7 +42,7 @@ @medium8 = FactoryBot.create(:medium, :with_teachable, :with_editors, sort: "Nuesse", description: "Unveröffentlichtes Medium") - sign_in FactoryBot.create(:confirmed_user) + sign_in FactoryBot.create(:confirmed_user_en) User.last.subscribe_lecture!(@lecture1) Medium.reindex @@ -50,17 +65,15 @@ it "can search for all (released) media" do get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") expect(response.body).not_to include("Unveröffentlichtes Medium") - hits = parse_media_search(response) - expect(hits).to eq(Medium.where(released: "all").size) + expect_all_results(response) end it "can search for media by title" do @params[:search][:fulltext] = "Erstes" get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") + expect(response.body).not_to include(NO_HITS_MSG) expect(response.body).to include("Erstes Medium") hits = parse_media_search(response) expect(hits).to eq(2) @@ -71,7 +84,7 @@ @params[:search][:types] = ["Quiz"] get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") + expect(response.body).not_to include(NO_HITS_MSG) expect(response.body).to include("Drittes Medium") hits = parse_media_search(response) expect(hits).to eq(1) @@ -82,7 +95,7 @@ @params[:search][:tag_ids] = @medium4.tags.pluck(:id) get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") + expect(response.body).not_to include(NO_HITS_MSG) expect(response.body).to include("Getagtes Medium") hits = parse_media_search(response) expect(hits).to eq(1) @@ -90,35 +103,40 @@ it 'can do combined search with tagoperator "or" and description' do @params[:search][:all_tags] = 0 - @params[:search][:tag_ids] = @medium4.tags.pluck(:id) + @params[:search][:tag_ids] = [@medium4.tags.pluck(:id), @medium5.tags.pluck(:id)].flatten @params[:search][:fulltext] = "Medium" get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") + expect(response.body).not_to include(NO_HITS_MSG) + expect(response.body).to include("Getagtes Medium") hits = parse_media_search(response) - expect(hits).to eq(Medium.where(released: "all").size) + expect(hits).to eq(2) end it 'can do search with tagoperator "and" and description' do @params[:search][:tag_operator] = "and" @params[:search][:all_tags] = 0 - @params[:search][:tag_ids] = @medium4.tags.pluck(:id) + @params[:search][:tag_ids] = [@tag1, @tag2].map(&:id) @params[:search][:fulltext] = "Medium" get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") - expect(response.body).to include("Getagtes Medium") - hits = parse_media_search(response) - expect(hits).to eq(1) + expect_no_results(response) end - it 'can search for all media with tags by using tagoperator "and"' do + it '"all tags" has higher precedence than any tagoperator (here "and")' do + @params[:search][:all_tags] = 1 @params[:search][:tag_operator] = "and" get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") - hits = parse_media_search(response) - expect(hits).to eq(2) + expect_all_results(response) + end + + it '"all tags" has higher precedence than any tagoperator (here "or")' do + @params[:search][:all_tags] = 1 + @params[:search][:tag_operator] = "or" + get media_search_path, params: @params + + expect_all_results(response) end it "can search by teacher" do @@ -126,7 +144,7 @@ @params[:search][:teacher_ids] = [@lecture1.teacher.id] get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") + expect(response.body).not_to include(NO_HITS_MSG) hits = parse_media_search(response) expect(hits).to eq(2) end @@ -135,7 +153,7 @@ @params[:search][:lecture_option] = 1 get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") + expect(response.body).not_to include(NO_HITS_MSG) expect(response.body).to include("Erstes Medium mit Lehrer") hits = parse_media_search(response) expect(hits).to eq(1) @@ -146,7 +164,7 @@ @params[:search][:media_lectures] = [@lecture2.id] get media_search_path, params: @params - expect(response.body).not_to include("The search has not returned any hits") + expect(response.body).not_to include(NO_HITS_MSG) expect(response.body).to include("Zweites Medium mit Lehrer") hits = parse_media_search(response) expect(hits).to eq(1) diff --git a/spec/rspec_inside_docker.py b/spec/rspec_inside_docker.py new file mode 100644 index 000000000..b6d78a168 --- /dev/null +++ b/spec/rspec_inside_docker.py @@ -0,0 +1,61 @@ +import sys +import subprocess + +# Path where the custom formatter of the Ruby Test explorer extension is memory-mapped +FORMATTER_PATH_IN_DOCKER = "/root/tmp/formatter.rb" +PROJECT_ROOT_FOLDER_NAME = "mampf" +DOCKER_SERVICE_NAME = "mampf" + + +def switch_formatter_path(): + """ + Memory-maps the custom formatter of the Ruby Test explorer extension. + + The Ruby Test explorer extension uses a custom formatter to get a specific + output JSON format. The path to the formatter is automatically passed as an + argument to the rspec command by the extension. We need to replace this + path by the path to the memory-mapped formatter in the Docker container. + """ + + formatter_argument_index = None + for i, arg in enumerate(sys.argv): + if arg == '--require': + formatter_argument_index = i + break + + if formatter_argument_index == None: + print('Please specify "--require path/to/custom/formatter.rb"') + sys.exit(1) + + # Switch the path to the custom formatter to the memory-mapped path + formatter_path_on_host = sys.argv[formatter_argument_index + 1] + sys.argv[formatter_argument_index + 1] = FORMATTER_PATH_IN_DOCKER + + return formatter_path_on_host + + +def process_paths(): + for i, arg in enumerate(sys.argv): + if not arg.startswith('./') and "spec" in arg: + sys.argv[i] = replace_absolute_paths_by_relative_paths(sys.argv[i]) + + +def replace_absolute_paths_by_relative_paths(path): + res = path.split(PROJECT_ROOT_FOLDER_NAME + '/')[1] + if not res: + print(f'Path {path}' + f' does not contain string "{PROJECT_ROOT_FOLDER_NAME}/"') + sys.exit(1) + return './' + res # make path relative + + +if __name__ == '__main__': + formatter_path_on_host = switch_formatter_path() + process_paths() + + rspec_args = ' '.join(sys.argv[1:]) + test_command = f'RAILS_ENV=test bundle exec rspec {rspec_args}' + + docker_cmd = f'cd ./docker/test && docker compose run --rm -T --entrypoint="" -v {formatter_path_on_host}:{FORMATTER_PATH_IN_DOCKER} {DOCKER_SERVICE_NAME} sh -c "{test_command}"' + + subprocess.call(docker_cmd, shell=True) diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb index 36ccc0309..a8949daa8 100644 --- a/spec/support/database_cleaner.rb +++ b/spec/support/database_cleaner.rb @@ -1,17 +1,15 @@ +# See the docs here: https://www.rubydoc.info/github/DatabaseCleaner/database_cleaner RSpec.configure do |config| config.before(:suite) do - DatabaseCleaner.clean_with(:truncation, except: ["ar_internal_metadata"]) - end - - config.before(:each) do DatabaseCleaner.strategy = :transaction + DatabaseCleaner.clean_with(:truncation) end config.before(:each) do DatabaseCleaner.start end - config.after(:each) do + config.append_after(:each) do DatabaseCleaner.clean end end diff --git a/spec/support/request_parse_helper.rb b/spec/support/request_parse_helper.rb index 5a541afce..d19f8666d 100644 --- a/spec/support/request_parse_helper.rb +++ b/spec/support/request_parse_helper.rb @@ -2,12 +2,12 @@ module RequestParsingHelper # Parse the request body as HTML and return the number of hits def parse_media_search(response) search_results = response.body - .match(/(?<=search_results.innerHTML = ').*(?=';)/)[0] + .match(/(?<=searchResults.innerHTML = ').*(?=';)/)[0] .gsub('\n', "") # strip whitespaces in search_results search_results = search_results.gsub(/\s+/, " ") # fix " in search_results - search_results = search_results.gsub('\\\"', '"') + search_results = search_results.gsub(/\\\"/, '"') # rubocop:disable Style/RedundantRegexpArgument,Style/RedundantRegexpEscape # fix \/ in search_results search_results = search_results.gsub('\\/', "/") @@ -15,9 +15,9 @@ def parse_media_search(response) search_results = Nokogiri::HTML(search_results) # get text within first "col-12 col-lg-2" div - matches = search_results.css("div.col-12.col-lg-2").first.text + num_hits_text = search_results.css("div.col-12.col-lg-2").first.text # get number in matches - matches.match(/\d+/)[0].to_i + num_hits_text.match(/\d+/)[0].to_i end end