diff --git a/CHANGELOG.md b/CHANGELOG.md index a966015..1ce4e0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.4.0 + +- Add support for ActiveJob +- Fix pretty formatting of payloads + ## 0.3.1 - Add optional database config (thanks to @kiskoza). diff --git a/Gemfile b/Gemfile index d331517..20017d4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } +gem 'rubocop', group: 'development', require: false gem "sqlite3" gem "sprockets-rails" gem "pagy" diff --git a/Gemfile.lock b/Gemfile.lock index a2f19a1..22e65ae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,9 @@ PATH remote: . specs: - eyeloupe (0.3.1) + eyeloupe (0.4.0) importmap-rails (~> 1.1) + nokogiri (~> 1.15.4) pagy (~> 6.0) rails (~> 7.0) ruby-openai (~> 4.1.0) @@ -76,6 +77,7 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + ast (2.4.2) builder (3.2.4) concurrent-ruby (1.2.2) crass (1.0.6) @@ -94,6 +96,7 @@ GEM importmap-rails (1.1.6) actionpack (>= 6.0.0) railties (>= 6.0.0) + json (2.6.2) loofah (2.19.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -117,11 +120,14 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.9) - nokogiri (1.14.2-arm64-darwin) + nokogiri (1.15.4-arm64-darwin) racc (~> 1.4) - nokogiri (1.14.2-x86_64-linux) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) pagy (6.0.4) + parallel (1.22.1) + parser (3.1.2.0) + ast (~> 2.4.1) racc (1.6.2) rack (2.2.7) rack-test (2.1.0) @@ -152,10 +158,26 @@ GEM rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) + rainbow (3.1.1) rake (13.0.6) + regexp_parser (2.8.0) + rexml (3.2.5) + rubocop (1.31.2) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.1.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.18.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.19.1) + parser (>= 3.1.1.0) ruby-openai (4.1.0) faraday (>= 1) faraday-multipart (>= 1) + ruby-progressbar (1.11.0) ruby2_keywords (0.0.5) sprockets (4.2.0) concurrent-ruby (~> 1.0) @@ -178,6 +200,7 @@ GEM railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + unicode-display_width (2.2.0) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -185,11 +208,13 @@ GEM PLATFORMS arm64-darwin-22 + arm64-darwin-23 x86_64-linux DEPENDENCIES eyeloupe! pagy + rubocop sprockets-rails sqlite3 tailwindcss-rails (~> 2.0) diff --git a/README.md b/README.md index ab0b537..795d014 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Logo -

Eyeloupe (beta)

+

Eyeloupe

The elegant Rails debug assistant. AI powered. @@ -141,13 +141,6 @@ Eyeloupe is not a performance-oriented tool, the request time is the same you ca Yes, Eyeloupe is inspired by Laravel Telescope. A lot of people coming from Laravel are missing Telescope or looking for something similar, so Eyeloupe is here to fill this gap. -## Roadmap - -- [x] Exceptions - Track all the exceptions thrown by your application -- [x] AI assistant - Use OpenAI API to help you to solve your exceptions -- [ ] Custom links to the menu - To access all of your debug tool in one place (Sidekiq web, Mailhog, etc.) -- [ ] Refactoring / clean code - To make the code more readable and maintainable - ## Contributing Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. @@ -165,7 +158,7 @@ The gem is available as open source under the terms of the [MIT License](https:/ ## Contact -[![](https://img.shields.io/badge/@alxlion__-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/alxlion_) +[![](https://img.shields.io/badge/@alxlion__-000000?style=for-the-badge&logo=x&logoColor=white)](https://x.com/alxlion_) Project Link: [https://github.com/alxlion/eyeloupe](https://github.com/alxlion/eyeloupe) diff --git a/app/controllers/concerns/eyeloupe/searchable.rb b/app/controllers/concerns/eyeloupe/searchable.rb index 4ec09cf..efe6f6f 100644 --- a/app/controllers/concerns/eyeloupe/searchable.rb +++ b/app/controllers/concerns/eyeloupe/searchable.rb @@ -4,6 +4,9 @@ module Eyeloupe module Searchable extend ActiveSupport::Concern + path_models = %w[InRequest OutRequest] + name_models = %w[Job] + included do before_action :set_query, only: [:index] end @@ -12,7 +15,8 @@ module Searchable def set_query model = ("Eyeloupe::" + controller_name.classify).constantize - @query = params[:q].present? ? model.where('path LIKE ?', "%#{params[:q].strip}%").order(created_at: :desc) + where = model.attribute_names.include?("path") ? 'path' : 'classname' + @query = params[:q].present? ? model.where("#{where} LIKE ?", "%#{params[:q].strip}%").order(created_at: :desc) : model.all.order(created_at: :desc) end end diff --git a/app/controllers/eyeloupe/jobs_controller.rb b/app/controllers/eyeloupe/jobs_controller.rb new file mode 100644 index 0000000..7efbcac --- /dev/null +++ b/app/controllers/eyeloupe/jobs_controller.rb @@ -0,0 +1,22 @@ +module Eyeloupe + class JobsController < ApplicationController + include Searchable + + before_action :set_job, only: %i[ show ] + + def index + @pagy, @jobs = pagy(@query, items: 50) + + render partial: 'frame' if params[:frame].present? + end + + def show + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_job + @job = Job.find(params[:id]) + end + end +end diff --git a/app/helpers/eyeloupe/jobs_helper.rb b/app/helpers/eyeloupe/jobs_helper.rb new file mode 100644 index 0000000..8d00951 --- /dev/null +++ b/app/helpers/eyeloupe/jobs_helper.rb @@ -0,0 +1,4 @@ +module Eyeloupe + module JobsHelper + end +end diff --git a/app/helpers/eyeloupe/request_helper.rb b/app/helpers/eyeloupe/request_helper.rb new file mode 100644 index 0000000..e81c5a4 --- /dev/null +++ b/app/helpers/eyeloupe/request_helper.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Eyeloupe + module RequestHelper + # @param [Eyeloupe::InRequest, Eyeloupe::OutRequest] request The request object + # @return [String] The formatted response + def format_response(request) + type = request.format.to_s != '*/*' ? request.format.to_s : request&.headers + format(type, request.response) + end + + # @param [Eyeloupe::InRequest, Eyeloupe::OutRequest] request The request object + # @return [String] The formatted payload + def format_payload(request) + type = request.format.to_s != '*/*' ? request.format.to_s : request&.headers + format(type, request.payload) + end + + private + + def format(format, str) + case format + when /json/ + JSON.pretty_generate(JSON.parse(str || '{}')) + when /xml/ + Nokogiri::XML(str || '<>').to_xml(indent: 2) + else + str + end + end + end +end diff --git a/app/models/eyeloupe/job.rb b/app/models/eyeloupe/job.rb new file mode 100644 index 0000000..10c0812 --- /dev/null +++ b/app/models/eyeloupe/job.rb @@ -0,0 +1,7 @@ +module Eyeloupe + class Job < ApplicationRecord + validates :job_id, uniqueness: true + + enum status: [:enqueued, :running, :completed, :failed, :discarded] + end +end diff --git a/app/views/eyeloupe/in_requests/show.html.erb b/app/views/eyeloupe/in_requests/show.html.erb index 14f0bf8..0e117bd 100644 --- a/app/views/eyeloupe/in_requests/show.html.erb +++ b/app/views/eyeloupe/in_requests/show.html.erb @@ -53,7 +53,7 @@

Payload
<% if @request.payload.present? %> -
<%= @request.payload %>
+
<%= format_payload @request %>
<% else %>

No payload

<% end %> @@ -74,7 +74,7 @@
Response
-
<%= @request.format == "application/json" ? JSON.pretty_generate(JSON.parse(@request.response || "{}")) : @request.response %>
+
<%= format_response @request %>
diff --git a/app/views/eyeloupe/jobs/_frame.html.erb b/app/views/eyeloupe/jobs/_frame.html.erb new file mode 100644 index 0000000..6424888 --- /dev/null +++ b/app/views/eyeloupe/jobs/_frame.html.erb @@ -0,0 +1,46 @@ +<%= turbo_frame_tag "frame" do %> +
+
+ + + + + + + + + + + + + + + <% @jobs.each do |job| %> + + + + + + + + + + <% end %> + +
NameQueueAdapterStatusRetryEnqueued at + Details +
+ <%= job.classname %> + <%= job.queue_name %><%= job.adapter %> + <%= render "eyeloupe/shared/job_status", job: job %> + <%= job.retry %><%= distance_of_time_in_words(job.created_at, DateTime.now) %> + <%= link_to "Details", job_path(job), class: "text-gray-600 hover:text-gray-900", data: {"turbo_frame": "_top"} %> +
+
+ +
+<% end %> \ No newline at end of file diff --git a/app/views/eyeloupe/jobs/index.html.erb b/app/views/eyeloupe/jobs/index.html.erb new file mode 100644 index 0000000..8950b55 --- /dev/null +++ b/app/views/eyeloupe/jobs/index.html.erb @@ -0,0 +1,18 @@ +
+
+
+

Jobs

+

All jobs running in your application

+
+
+
+ + + +
+
+
+
+ <%= turbo_frame_tag "frame", src: jobs_path(frame: true), data: {"eyeloupe--refresh-target": "frame", "eyeloupe--search-target": "frame"} do %><% end %> +
+
diff --git a/app/views/eyeloupe/jobs/show.html.erb b/app/views/eyeloupe/jobs/show.html.erb new file mode 100644 index 0000000..989c357 --- /dev/null +++ b/app/views/eyeloupe/jobs/show.html.erb @@ -0,0 +1,63 @@ +
+
+

Job details

+
+
+
+
+
Enqueued at
+
+ <%= @job.created_at.to_formatted_s(:long) %> (<%= distance_of_time_in_words(@job.created_at, DateTime.now) %>) +
+
+
+
Duration
+
+ <%= (@job.completed_at - @job.created_at).round %> seconds +
+
+
+
Name
+
<%= @job.classname %>
+
+
+
Adapter
+
+ <%= @job.adapter %> +
+
+
+
Queue
+
<%= @job.queue_name %>
+
+
+
Job ID
+
+ <%= @job.job_id %> +
+
+
+
Retry
+
+ <%= @job.retry %> +
+
+
+
Status
+
+ <%= render "eyeloupe/shared/job_status", job: @job %> +
+
+
+
Arguments
+
+ <% if @job.args.present? %> +
<%= JSON.pretty_generate(JSON.parse(@job.args || "{}")) %>
+ <% else %> +

No args

+ <% end %> +
+
+
+
+
\ No newline at end of file diff --git a/app/views/eyeloupe/out_requests/show.html.erb b/app/views/eyeloupe/out_requests/show.html.erb index be5c3c6..3ef7cff 100644 --- a/app/views/eyeloupe/out_requests/show.html.erb +++ b/app/views/eyeloupe/out_requests/show.html.erb @@ -40,7 +40,7 @@
Payload
<% if @request.payload.present? %> -
<%= @request.payload %>
+
<%= format_payload @request %>
<% else %>

No payload

<% end %> @@ -61,7 +61,7 @@
Response
-
<%= @request.format == "application/json" ? JSON.pretty_generate(JSON.parse(@request.response || "{}")) : @request.response %>
+
<%= format_response @request %>
diff --git a/app/views/eyeloupe/shared/_job_status.html.erb b/app/views/eyeloupe/shared/_job_status.html.erb new file mode 100644 index 0000000..1e0991a --- /dev/null +++ b/app/views/eyeloupe/shared/_job_status.html.erb @@ -0,0 +1,21 @@ +<% if job.enqueued? %> + + <%= job.status.capitalize %> + +<% elsif job.running? %> + + <%= job.status.capitalize %> + +<% elsif job.completed? %> + + <%= job.status.capitalize %> + +<% elsif job.failed? %> + + <%= job.status.capitalize %> + +<% else %> + + <%= job.status.capitalize %> + +<% end %> \ No newline at end of file diff --git a/app/views/layouts/eyeloupe/application.html.erb b/app/views/layouts/eyeloupe/application.html.erb index 440c5b7..e6beeed 100644 --- a/app/views/layouts/eyeloupe/application.html.erb +++ b/app/views/layouts/eyeloupe/application.html.erb @@ -84,6 +84,16 @@ Exceptions <% end %> +
  • + <%= link_to jobs_path, class:"#{request.path.include?('/jobs') ? 'bg-gray-200 text-red-500' : ''} hover:bg-gray-200 text-gray-500 group flex gap-x-3 rounded-md p-2 text-base leading-6 font-medium" do %> + + + + + + Jobs + <% end %> +
  • @@ -148,6 +158,16 @@ Exceptions <% end %> +
  • + <%= link_to jobs_path, class:"#{request.path.include?('/jobs') ? 'bg-gray-200 text-red-500' : ''} hover:bg-gray-200 text-gray-500 group flex gap-x-3 rounded-md p-2 text-base leading-6 font-medium" do %> + + + + + + Jobs + <% end %> +
  • diff --git a/config/routes.rb b/config/routes.rb index 2fa8fab..690d0a7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,7 @@ resources :in_requests, only: [:index, :show] resources :out_requests, only: [:index, :show] resources :exceptions, only: [:index, :show] + resources :jobs, only: [:index, :show] resources :ai_assistant_responses, only: [:show] resource :data, only: [:destroy] diff --git a/db/migrate/20230827161224_create_eyeloupe_jobs.rb b/db/migrate/20230827161224_create_eyeloupe_jobs.rb new file mode 100644 index 0000000..437f91b --- /dev/null +++ b/db/migrate/20230827161224_create_eyeloupe_jobs.rb @@ -0,0 +1,19 @@ +class CreateEyeloupeJobs < ActiveRecord::Migration[7.0] + def change + create_table :eyeloupe_jobs do |t| + t.string :classname + t.string :job_id + t.string :queue_name + t.string :adapter + t.integer :status, default: 0 + t.datetime :scheduled_at + t.datetime :executed_at + t.datetime :completed_at + t.integer :retry, default: 0 + t.string :args + t.timestamps + + t.index :job_id, unique: true + end + end +end diff --git a/doc/img/screen.png b/doc/img/screen.png index f582673..8b6326c 100644 Binary files a/doc/img/screen.png and b/doc/img/screen.png differ diff --git a/eyeloupe.gemspec b/eyeloupe.gemspec index 3ab4b94..d26ff70 100644 --- a/eyeloupe.gemspec +++ b/eyeloupe.gemspec @@ -25,6 +25,7 @@ Gem::Specification.new do |spec| spec.add_dependency "importmap-rails", "~> 1.1" spec.add_dependency "pagy", "~> 6.0" spec.add_dependency "ruby-openai", "~> 4.1.0" + spec.add_dependency "nokogiri", "~> 1.15.4" spec.add_development_dependency "sqlite3", "~> 1.3.6" spec.add_development_dependency "tailwindcss-rails", "~> 2.0" diff --git a/lib/eyeloupe.rb b/lib/eyeloupe.rb index b8d0e81..dc6a91d 100644 --- a/lib/eyeloupe.rb +++ b/lib/eyeloupe.rb @@ -6,6 +6,7 @@ require 'eyeloupe/processors/in_request' require 'eyeloupe/processors/out_request' require 'eyeloupe/processors/exception' +require 'eyeloupe/processors/job' require 'eyeloupe/concerns/rescuable' require 'pagy' diff --git a/lib/eyeloupe/engine.rb b/lib/eyeloupe/engine.rb index 25f8ba2..81bd879 100644 --- a/lib/eyeloupe/engine.rb +++ b/lib/eyeloupe/engine.rb @@ -15,6 +15,30 @@ class Engine < ::Rails::Engine initializer 'eyeloupe.active_job' do ActiveSupport.on_load(:active_job) do include Eyeloupe::Concerns::Rescuable + + ActiveSupport::Notifications.subscribe("enqueue_at.active_job") do |*args| + Eyeloupe::Processors::Job.instance.process(ActiveSupport::Notifications::Event.new(*args)) + end + + ActiveSupport::Notifications.subscribe("enqueue.active_job") do |*args| + Eyeloupe::Processors::Job.instance.process(ActiveSupport::Notifications::Event.new(*args)) + end + + ActiveSupport::Notifications.subscribe("perform_start.active_job") do |*args| + Eyeloupe::Processors::Job.instance.run(ActiveSupport::Notifications::Event.new(*args)) + end + + ActiveSupport::Notifications.subscribe("perform.active_job") do |*args| + Eyeloupe::Processors::Job.instance.complete(ActiveSupport::Notifications::Event.new(*args)) + end + + ActiveSupport::Notifications.subscribe("retry_stopped.active_job") do |*args| + Eyeloupe::Processors::Job.instance.failed(ActiveSupport::Notifications::Event.new(*args)) + end + + ActiveSupport::Notifications.subscribe("discard.active_job") do |*args| + Eyeloupe::Processors::Job.instance.discard(ActiveSupport::Notifications::Event.new(*args)) + end end end diff --git a/lib/eyeloupe/processors/job.rb b/lib/eyeloupe/processors/job.rb new file mode 100644 index 0000000..210b098 --- /dev/null +++ b/lib/eyeloupe/processors/job.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true +module Eyeloupe + module Processors + class Job + include Singleton + + # @return [Array] + attr_accessor :subs + + def initialize + @subs = [] + end + + # @param [ActiveSupport::Notifications::Event] event The event object + def process(event) + job = event.payload[:job] + + Eyeloupe::Job.create( + classname: job.class.name, + job_id: job.job_id, + queue_name: queue_name(event), + adapter: adapter_name(event), + scheduled_at: scheduled_at(event), + status: :enqueued, + args: (args_info(job) || {}).to_json + ) + end + + # @param [ActiveSupport::Notifications::Event] event The event object + def run(event) + job = event.payload[:job] + + Eyeloupe::Job.where(job_id: job.job_id).update(status: :running, executed_at: Time.now.utc) + end + + # @param [ActiveSupport::Notifications::Event] event The event object + def complete(event) + job = event.payload[:job] + + existing = Eyeloupe::Job.where(job_id: job.job_id).first + + if existing&.failed? + Eyeloupe::Job.where(job_id: job.job_id).update(completed_at: Time.now.utc, retry: existing.retry + 1) + else + Eyeloupe::Job.where(job_id: job.job_id).update( + status: :completed, + completed_at: Time.now.utc, + retry: (job.executions.zero? ? 1 : job.executions) - 1 + ) + end + end + + # @param [ActiveSupport::Notifications::Event] event The event object + def failed(event) + job = event.payload[:job] + + Eyeloupe::Job.where(job_id: job.job_id).update(status: :failed) + end + + # @param [ActiveSupport::Notifications::Event] event The event object + def discard(event) + job = event.payload[:job] + + Eyeloupe::Job.where(job_id: job.job_id).update(status: :discarded) + end + + # @param [ActiveJob::Base] job The job object + # @return [Array, nil] + def args_info(job) + if job.class.log_arguments? && job.arguments.any? + job.arguments + end + end + + private + + # @param [ActiveSupport::Notifications::Event] event The event object + # @return [String] The name of the queue + def queue_name(event) + event.payload[:job].queue_name + end + + # @param [ActiveSupport::Notifications::Event] event The event object + # @return [String] The name of the adapter + def adapter_name(event) + event.payload[:adapter].class.name.demodulize.remove("Adapter") + end + + # @param [ActiveSupport::Notifications::Event] event The event object + # @return [Time, nil] The time the job was scheduled at + def scheduled_at(event) + return unless event.payload[:job].scheduled_at + Time.at(event.payload[:job].scheduled_at).utc + end + + # @param [String] event The event to subscribe to + # @param [Proc] block The block to execute when the event is triggered + # @yield [ActiveSupport::Notifications::Event] The event object + def subscribe(event, &block) + @subs << ActiveSupport::Notifications.subscribe(event) do |*args| + block.call(ActiveSupport::Notifications::Event.new(*args)) + end + end + + def unsubscribe + @subs.each do |sub| + ActiveSupport::Notifications.unsubscribe(sub) + end + @subs = [] + end + end + end +end \ No newline at end of file diff --git a/lib/eyeloupe/version.rb b/lib/eyeloupe/version.rb index 911d821..e85e5f7 100644 --- a/lib/eyeloupe/version.rb +++ b/lib/eyeloupe/version.rb @@ -1,4 +1,4 @@ module Eyeloupe # @return [String] - VERSION = "0.3.1" + VERSION = "0.4.0" end diff --git a/test/controllers/eyeloupe/in_requests_controller_test.rb b/test/controllers/eyeloupe/in_requests_controller_test.rb index 63b26ea..0db3b5b 100644 --- a/test/controllers/eyeloupe/in_requests_controller_test.rb +++ b/test/controllers/eyeloupe/in_requests_controller_test.rb @@ -4,15 +4,19 @@ module Eyeloupe class InRequestsControllerTest < ActionDispatch::IntegrationTest - fixtures "eyeloupe/in_requests" + include Engine.routes.url_helpers + + setup do + @in_request = eyeloupe_in_requests(:one) + end test "should get index" do - get eyeloupe.in_requests_url + get in_requests_url assert_response :success end test "should get show" do - get eyeloupe.in_request_url(eyeloupe_in_requests(:one)) + get in_request_url(@in_request) assert_response :success end end diff --git a/test/controllers/eyeloupe/jobs_controller_test.rb b/test/controllers/eyeloupe/jobs_controller_test.rb new file mode 100644 index 0000000..8b3cfba --- /dev/null +++ b/test/controllers/eyeloupe/jobs_controller_test.rb @@ -0,0 +1,21 @@ +require "test_helper" + +module Eyeloupe + class JobsControllerTest < ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + + setup do + @job = eyeloupe_jobs(:one) + end + + test "should get index" do + get jobs_url + assert_response :success + end + + test "should get show" do + get jobs_url(@job) + assert_response :success + end + end +end diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb index 2ddd659..3a2b877 100644 --- a/test/dummy/app/helpers/application_helper.rb +++ b/test/dummy/app/helpers/application_helper.rb @@ -1,3 +1,2 @@ module ApplicationHelper - end \ No newline at end of file diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index 504da8f..69369ca 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_06_04_190442) do +ActiveRecord::Schema[7.0].define(version: 2023_08_27_161224) do create_table "eyeloupe_exceptions", force: :cascade do |t| t.string "hostname" t.string "kind" @@ -49,6 +49,22 @@ t.datetime "updated_at", null: false end + create_table "eyeloupe_jobs", force: :cascade do |t| + t.string "classname" + t.string "job_id" + t.string "queue_name" + t.string "adapter" + t.integer "status", default: 0 + t.datetime "scheduled_at" + t.datetime "executed_at" + t.datetime "completed_at" + t.integer "retry", default: 0 + t.string "args" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["job_id"], name: "index_eyeloupe_jobs_on_job_id", unique: true + end + create_table "eyeloupe_out_requests", force: :cascade do |t| t.string "verb" t.string "hostname" diff --git a/test/fixtures/eyeloupe/jobs.yml b/test/fixtures/eyeloupe/jobs.yml new file mode 100644 index 0000000..0b6ef49 --- /dev/null +++ b/test/fixtures/eyeloupe/jobs.yml @@ -0,0 +1,23 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + classname: MyString + job_id: MyString + queue_name: MyString + adapter: MyString + status: 0 + retry: 0 + executed_at: 2023-08-27 18:12:24 + completed_at: 2023-08-27 18:12:24 + scheduled_at: 2023-08-27 18:12:24 + +two: + classname: MyString + job_id: MyString2 + queue_name: MyString + adapter: MyString + status: 0 + retry: 0 + executed_at: 2023-08-27 18:12:24 + completed_at: 2023-08-27 18:12:24 + scheduled_at: 2023-08-27 18:12:24 diff --git a/test/lib/processors/job_processor_test.rb b/test/lib/processors/job_processor_test.rb new file mode 100644 index 0000000..17a30e5 --- /dev/null +++ b/test/lib/processors/job_processor_test.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "test_helper" +include ActiveJob::TestHelper + +class JobProcessorTest < ActiveSupport::TestCase + def setup + @job_processor = Eyeloupe::Processors::Job.instance + + activejob = ActiveJob::Base.new + activejob.job_id = SecureRandom.uuid + @event = ActiveSupport::Notifications::Event.new("test.event", Time.now, Time.now, SecureRandom.uuid, {job: activejob}) + end + + test "should initialize job processor" do + assert_not_nil @job_processor + assert_equal [], @job_processor.subs + end + + test "should process" do + job = @job_processor.process(@event) + assert_not_nil job + assert job.persisted? + end + + test "should run" do + job = @job_processor.process(@event) + @job_processor.run(@event) + job.reload + assert_equal "running", job.status + end + + test "should complete without fail" do + job = @job_processor.process(@event) + @job_processor.run(@event) + @job_processor.complete(@event) + job.reload + assert_equal "completed", job.status + assert_equal 0, job.retry + end + + test "should complete with fail" do + job = @job_processor.process(@event) + @job_processor.run(@event) + job.update(status: :failed) + @job_processor.complete(@event) + job.reload + assert_equal "failed", job.status + assert_equal 1, job.retry + end + + test "should be failed" do + job = @job_processor.process(@event) + @job_processor.failed(@event) + job.reload + assert_equal "failed", job.status + end + + test "should be discarded" do + job = @job_processor.process(@event) + @job_processor.discard(@event) + job.reload + assert_equal "discarded", job.status + end + +end diff --git a/test/models/eyeloupe/job_test.rb b/test/models/eyeloupe/job_test.rb new file mode 100644 index 0000000..f5cdb4e --- /dev/null +++ b/test/models/eyeloupe/job_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +module Eyeloupe + class JobTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end + end +end diff --git a/test/system/eyeloupe/out_requests_test.rb b/test/system/eyeloupe/out_requests_test.rb deleted file mode 100644 index b0c5fda..0000000 --- a/test/system/eyeloupe/out_requests_test.rb +++ /dev/null @@ -1,14 +0,0 @@ -require "application_system_test_case" - -module Eyeloupe - class OutRequestsTest < ApplicationSystemTestCase - setup do - @out_request = eyeloupe_out_requests(:one) - end - - test "visiting the index" do - visit out_requests_url - assert_selector "h1", text: "HTTP Client" - end - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb index 7230d9c..32a9028 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,6 +6,8 @@ ActiveRecord::Migrator.migrations_paths << File.expand_path("../db/migrate", __dir__) require "rails/test_help" +include Eyeloupe::RequestHelper + # Load fixtures from the engine if ActiveSupport::TestCase.respond_to?(:fixture_path=) ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)