From 59e31baa1b71f38c61bc8ddd719ada31c2256f63 Mon Sep 17 00:00:00 2001 From: Marcin Kulik Date: Sun, 4 Aug 2013 22:53:25 +0200 Subject: [PATCH] Process incoming asciicast with AsciicastCreator --- app/controllers/api/asciicasts_controller.rb | 14 ++-- app/models/asciicast.rb | 19 +----- app/services/asciicast_creator.rb | 39 +++++++++++ .../api/asciicasts_controller_spec.rb | 58 ++++++++--------- spec/fixtures/meta.json | 15 +++++ spec/models/asciicast_spec.rb | 64 ------------------- spec/services/asciicast_creator_spec.rb | 56 ++++++++++++++++ spec/support/feature_helpers.rb | 3 +- 8 files changed, 143 insertions(+), 125 deletions(-) create mode 100644 app/services/asciicast_creator.rb create mode 100644 spec/fixtures/meta.json create mode 100644 spec/services/asciicast_creator_spec.rb diff --git a/app/controllers/api/asciicasts_controller.rb b/app/controllers/api/asciicasts_controller.rb index 46b5105..a264dee 100644 --- a/app/controllers/api/asciicasts_controller.rb +++ b/app/controllers/api/asciicasts_controller.rb @@ -1,15 +1,13 @@ class Api::AsciicastsController < ApplicationController + skip_before_filter :verify_authenticity_token def create - ac = Asciicast.new(params[:asciicast]) - - if ac.save - SnapshotWorker.perform_async(ac.id) - render :text => asciicast_url(ac), :status => :created, :location => ac - else - render :text => ac.errors.full_messages, :status => 422 - end + asciicast = AsciicastCreator.new.create(params[:asciicast]) + render :text => asciicast_url(asciicast), :status => :created, + :location => asciicast + rescue ActiveRecord::RecordInvalid => e + render :nothing => true, :status => 422 end end diff --git a/app/models/asciicast.rb b/app/models/asciicast.rb index 1acccaa..03018a5 100644 --- a/app/models/asciicast.rb +++ b/app/models/asciicast.rb @@ -29,8 +29,7 @@ class Asciicast < ActiveRecord::Base before_create :assign_user, :unless => :user - attr_accessible :meta, :stdout, :stdout_timing, :stdin, :stdin_timing, - :title, :description, :time_compression + attr_accessible :title, :description, :time_compression def self.assign_user(user_token, user) where(:user_id => nil, :user_token => user_token). @@ -42,22 +41,6 @@ class Asciicast < ActiveRecord::Base Digest::MD5.hexdigest timestamps.join('/') end - def meta=(file) - data = JSON.parse(file.tempfile.read) - - self.username = data['username'] - self.user_token = data['user_token'] - self.duration = data['duration'] - self.recorded_at = data['recorded_at'] - self.title = data['title'] - self.command = data['command'] - self.shell = data['shell'] - self.uname = data['uname'] - self.terminal_lines = data['term']['lines'] - self.terminal_columns = data['term']['columns'] - self.terminal_type = data['term']['type'] - end - def assign_user if user_token.present? if ut = UserToken.find_by_token(user_token) diff --git a/app/services/asciicast_creator.rb b/app/services/asciicast_creator.rb new file mode 100644 index 0000000..75c00d2 --- /dev/null +++ b/app/services/asciicast_creator.rb @@ -0,0 +1,39 @@ +class AsciicastCreator + + def create(attributes) + attributes = prepare_attributes(attributes) + + Asciicast.create!(attributes, :without_protection => true).tap do |asciicast| + SnapshotWorker.perform_async(asciicast.id) + end + end + + private + + def prepare_attributes(attributes) + meta = parse_meta_file(attributes[:meta]) + + { + :stdout_data => attributes[:stdout], + :stdout_timing => attributes[:stdout_timing], + :stdin_data => attributes[:stdin], + :stdin_timing => attributes[:stdin_timing], + :username => meta['username'], + :user_token => meta['user_token'], + :duration => meta['duration'], + :recorded_at => meta['recorded_at'], + :title => meta['title'], + :command => meta['command'], + :shell => meta['shell'], + :uname => meta['uname'], + :terminal_lines => meta['term']['lines'], + :terminal_columns => meta['term']['columns'], + :terminal_type => meta['term']['type'], + } + end + + def parse_meta_file(file) + JSON.parse(file.read) + end + +end diff --git a/spec/controllers/api/asciicasts_controller_spec.rb b/spec/controllers/api/asciicasts_controller_spec.rb index c2558c8..e96e52a 100644 --- a/spec/controllers/api/asciicasts_controller_spec.rb +++ b/spec/controllers/api/asciicasts_controller_spec.rb @@ -3,57 +3,49 @@ require 'spec_helper' describe Api::AsciicastsController do describe '#create' do - - let(:asciicast) { stub_model(Asciicast, :id => 666) } + let(:creator) { double('creator') } + let(:attributes) { { 'foo' => 'bar' } } before do - new = asciicast - Asciicast.should_receive(:new).and_return(new) + allow(AsciicastCreator).to receive(:new).with(no_args()) { creator } end - context 'when save succeeds' do - before do - asciicast.stub(:save => true) - end + context 'when the creator returns an asciicast' do + let(:asciicast) { stub_model(Asciicast, :id => 666) } - it 'enqueues snapshot capture' do - post :create - SnapshotWorker.should have_queued_job(asciicast.id) + before do + allow(creator).to receive(:create).with(attributes) { asciicast } + post :create, :asciicast => attributes end - it 'returns status 201' do - post :create - - response.status.should == 201 + it 'returns the status 201' do + expect(response.status).to eq(201) end - it 'returns URL of created asciicast as content body' do - post :create - - response.body.should == asciicast_url(asciicast) + it 'returns the URL of created asciicast as the content body' do + expect(response.body).to eq(asciicast_url(asciicast)) end end - context 'when save fails' do + context 'when the creator raises ActiveRecord::RecordInvalid' do + let(:asciicast) { double('asciicast', :errors => errors) } + let(:errors) { double('errors', :full_messages => full_messages) } + let(:full_messages) { ['This is invalid'] } + before do - asciicast.stub(:save => false) + allow(creator).to receive(:create).with(attributes). + and_raise(ActiveRecord::RecordInvalid.new(asciicast)) + post :create, :asciicast => attributes end - it 'returns status 422' do - post :create - - response.status.should == 422 + it 'returns the status 422' do + expect(response.status).to eq(422) end - it 'returns full error messages as content body' do - full_messages = double.to_s - errors = double('errors', :full_messages => full_messages) - asciicast.should_receive(:errors).and_return(errors) - post :create - - response.body.should == full_messages + it 'returns the full error messages as the content body' do + expect(response.body).to be_blank end end - end + end diff --git a/spec/fixtures/meta.json b/spec/fixtures/meta.json new file mode 100644 index 0000000..f21ad5f --- /dev/null +++ b/spec/fixtures/meta.json @@ -0,0 +1,15 @@ +{ + "command": "/bin/bash", + "duration": 11.146430015563965, + "recorded_at": "Thu, 25 Jul 2013 20:08:57 +0000", + "shell": "/bin/zsh", + "term": { + "columns": 96, + "lines": 26, + "type": "screen-256color" + }, + "title": "bashing :)", + "uname": "Linux 3.9.9-302.fc19.x86_64 #1 SMP Sat Jul 6 13:41:07 UTC 2013 x86_64", + "user_token": "f33e6188-f53c-11e2-abf4-84a6c827e88b", + "username": "kill" +} diff --git a/spec/models/asciicast_spec.rb b/spec/models/asciicast_spec.rb index 4c146e9..29d067b 100644 --- a/spec/models/asciicast_spec.rb +++ b/spec/models/asciicast_spec.rb @@ -71,70 +71,6 @@ describe Asciicast do end end - describe '#meta=' do - let(:asciicast) { stub_model(Asciicast) } - - let(:username) { 'username' } - let(:user_token) { 'token' } - let(:duration) { 123.456 } - let(:recorded_at) { Time.now.to_s } - let(:title) { 'title' } - let(:command) { '/bin/command' } - let(:shell) { '/bin/shell' } - let(:uname) { 'OS' } - let(:terminal_lines) { 29 } - let(:terminal_columns) { 97 } - let(:terminal_type) { 'xterm-lolz' } - - it 'assigns attributes properly' do - data = { - :username => username, - :user_token => user_token, - :duration => duration, - :recorded_at => recorded_at, - :title => title, - :command => command, - :shell => shell, - :uname => uname, - :term => { - :lines => terminal_lines, - :columns => terminal_columns, - :type => terminal_type, - } - } - json = data.to_json - tempfile = stub('tempfile', :read => json) - json_file = stub('file', :tempfile => tempfile) - asciicast.meta = json_file - - asciicast.username.should == username - asciicast.user_token.should == user_token - asciicast.duration.should == duration - asciicast.recorded_at.should == recorded_at - asciicast.title.should == title - asciicast.command.should == command - asciicast.shell.should == shell - asciicast.uname.should == uname - asciicast.terminal_lines.should == terminal_lines - asciicast.terminal_columns.should == terminal_columns - asciicast.terminal_type.should == terminal_type - end - end - - describe '#snapshot' do - let(:asciicast) { Asciicast.new } - let(:snapshot) { - Snapshot.new([ - SnapshotLine.new([ - SnapshotFragment.new('foo', { :fg => 1 }) - ]) - ]) - } - - it 'is empty Snapshot instance initially' do - expect(asciicast.snapshot).to eq(Snapshot.new) - end - describe '#stdout' do let(:stdout) { double('stdout') } diff --git a/spec/services/asciicast_creator_spec.rb b/spec/services/asciicast_creator_spec.rb new file mode 100644 index 0000000..1382169 --- /dev/null +++ b/spec/services/asciicast_creator_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe AsciicastCreator do + let(:creator) { AsciicastCreator.new } + + describe '#create' do + let(:meta_file) { fixture_file_upload('spec/fixtures/meta.json', 'application/json') } + let(:stdout_data_file) { double('stdout_data_file') } + let(:stdout_timing_file) { double('stdout_timing_file') } + let(:asciicast) { stub_model(Asciicast, :id => 666) } + + subject { + creator.create( + :meta => meta_file, + :stdout => stdout_data_file, + :stdout_timing => stdout_timing_file + ) + } + + before do + allow(Asciicast).to receive(:create!) { asciicast } + end + + it 'calls Asciicast.create! with proper attributes' do + subject + + expect(Asciicast).to have_received(:create!).with({ + :stdout_data => stdout_data_file, + :stdout_timing => stdout_timing_file, + :stdin_data => nil, + :stdin_timing => nil, + :username => 'kill', + :user_token => 'f33e6188-f53c-11e2-abf4-84a6c827e88b', + :duration => 11.146430015563965, + :recorded_at => 'Thu, 25 Jul 2013 20:08:57 +0000', + :title => 'bashing :)', + :command => '/bin/bash', + :shell => '/bin/zsh', + :uname => 'Linux 3.9.9-302.fc19.x86_64 #1 SMP Sat Jul 6 13:41:07 UTC 2013 x86_64', + :terminal_columns => 96, + :terminal_lines => 26, + :terminal_type => 'screen-256color' + }, { :without_protection => true }) + end + + it 'enqueues snapshot capture job' do + subject + + expect(SnapshotWorker).to have_queued_job(asciicast.id) + end + + it 'returns the created asciicast' do + expect(subject).to be(asciicast) + end + end +end diff --git a/spec/support/feature_helpers.rb b/spec/support/feature_helpers.rb index ccdc912..b9b5d5a 100644 --- a/spec/support/feature_helpers.rb +++ b/spec/support/feature_helpers.rb @@ -10,8 +10,7 @@ module AsciiIo end def load_asciicast(id) - FactoryGirl.create( - :asciicast, + AsciicastCreator.new.create( :meta => uploaded_file( "spec/fixtures/asciicasts/#{id}/meta.json", 'application/json'