Merge branch 'master' into new-gui

Conflicts:
	Gemfile.lock
This commit is contained in:
Reinier Balt 2013-09-22 10:30:24 +02:00
commit 463f5e922d
37 changed files with 1083 additions and 501 deletions

View file

@ -10,7 +10,7 @@ before_install:
- "cp config/site.yml.tmpl config/site.yml"
- "cp config/database.yml.tmpl config/database.yml"
script: "bundle exec rake ci"
script: "CODECLIMATE_REPO_TOKEN=5c52fdd2bbcd0734d56ddb2c3cbaac782da345273e8689d25f54a065ccc3397c bundle exec rake ci RACK_ENV=test"
bundler_args: --without development
notifications:

View file

@ -67,7 +67,7 @@ group :test do
gem "database_cleaner"
gem "mocha", :require => false
gem "aruba", :require => false
gem "aruba", git: 'https://github.com/cucumber/aruba', :require => false # need 0.5.4 for piping files; 0.5.3 is latest
gem "simplecov"
gem "timecop", "~> 0.6.2"
@ -81,4 +81,7 @@ group :test do
# uncomment to be able to make screenshots from scenarios
#gem "capybara-screenshot"
#gem "launchy"
# get test coverage info on codeclimate
gem "codeclimate-test-reporter", group: :test, require: nil
end

View file

@ -1,3 +1,12 @@
GIT
remote: https://github.com/cucumber/aruba
revision: adbfc240d69254d7b525876b4c5bff6b721b7d65
specs:
aruba (0.5.3)
childprocess (>= 0.3.6)
cucumber (>= 1.1.1)
rspec-expectations (>= 2.7.0)
GIT
remote: https://github.com/rails/actionpack-xml_parser
revision: 246653ab3670f329176c1e77e6cd1a632466f06e
@ -9,7 +18,11 @@ GEM
remote: https://rubygems.org/
specs:
RedCloth (4.2.9)
<<<<<<< HEAD
aasm (3.0.20)
=======
aasm (3.0.22)
>>>>>>> master
actionmailer (4.0.0)
actionpack (= 4.0.0)
mail (~> 2.5.3)
@ -37,11 +50,15 @@ GEM
acts_as_list (0.3.0)
activerecord (>= 3.0)
arel (4.0.0)
<<<<<<< HEAD
aruba (0.5.3)
childprocess (>= 0.3.6)
cucumber (>= 1.1.1)
rspec-expectations (>= 2.7.0)
atomic (1.1.12)
=======
atomic (1.1.14)
>>>>>>> master
bcrypt-ruby (3.0.1)
builder (3.1.4)
bullet (4.6.0)
@ -57,6 +74,8 @@ GEM
xpath (~> 2.0)
childprocess (0.3.9)
ffi (~> 1.0, >= 1.0.11)
codeclimate-test-reporter (0.0.11)
simplecov (>= 0.7.1, < 1.0.0)
coffee-rails (4.0.0)
coffee-script (>= 2.2.0)
railties (>= 4.0.0.beta, < 5.0)
@ -64,61 +83,80 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.6.3)
<<<<<<< HEAD
commonjs (0.2.6)
cucumber (1.3.6)
=======
cucumber (1.3.8)
>>>>>>> master
builder (>= 2.1.2)
diff-lcs (>= 1.1.3)
gherkin (~> 2.12.0)
multi_json (~> 1.7.5)
gherkin (~> 2.12.1)
multi_json (>= 1.7.5, < 2.0)
multi_test (>= 0.0.2)
cucumber-rails (1.3.0)
cucumber-rails (1.4.0)
capybara (>= 1.1.2)
cucumber (>= 1.1.8)
cucumber (>= 1.2.0)
nokogiri (>= 1.5.0)
<<<<<<< HEAD
=======
rails (>= 3.0.0)
>>>>>>> master
database_cleaner (1.1.1)
diff-lcs (1.2.4)
erubis (2.7.0)
execjs (1.4.0)
multi_json (~> 1.0)
execjs (2.0.1)
factory_girl (4.2.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.2.1)
factory_girl (~> 4.2.0)
railties (>= 3.0.0)
ffi (1.9.0)
gherkin (2.12.0)
gherkin (2.12.1)
multi_json (~> 1.3)
hike (1.2.3)
htmlentities (4.3.1)
i18n (0.6.4)
i18n (0.6.5)
jquery-rails (3.0.4)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.8.0)
<<<<<<< HEAD
less (2.3.2)
commonjs (~> 0.2.6)
less-rails (2.3.3)
actionpack (>= 3.1)
less (~> 2.3.1)
libv8 (3.11.8.17)
=======
libv8 (3.16.14.3)
>>>>>>> master
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
metaclass (0.0.1)
mime-types (1.23)
mime-types (1.25)
mini_portile (0.5.1)
minitest (4.7.5)
mocha (0.14.0)
metaclass (~> 0.0.1)
<<<<<<< HEAD
mousetrap-rails (0.0.11)
multi_json (1.7.8)
=======
multi_json (1.8.0)
>>>>>>> master
multi_test (0.0.2)
mysql2 (0.3.13)
nokogiri (1.6.0)
mini_portile (~> 0.5.0)
polyglot (0.3.3)
rack (1.5.2)
<<<<<<< HEAD
rack-mini-profiler (0.1.28)
=======
rack-mini-profiler (0.1.31)
>>>>>>> master
rack (>= 1.1.3)
rack-test (0.6.2)
rack (>= 1.0)
@ -130,7 +168,7 @@ GEM
bundler (>= 1.3.0, < 2.0)
railties (= 4.0.0)
sprockets-rails (~> 2.0.0)
rails_autolink (1.1.0)
rails_autolink (1.1.3)
rails (> 3.1)
railties (4.0.0)
actionpack (= 4.0.0)
@ -139,10 +177,17 @@ GEM
thor (>= 0.18.1, < 2.0)
rake (10.1.0)
ref (1.0.5)
<<<<<<< HEAD
rspec-expectations (2.14.1)
diff-lcs (>= 1.1.3, < 2.0)
rubyzip (0.9.9)
safe_yaml (0.9.5)
=======
rspec-expectations (2.14.2)
diff-lcs (>= 1.1.3, < 2.0)
rubyzip (0.9.9)
safe_yaml (0.9.7)
>>>>>>> master
sanitize (2.0.6)
nokogiri (>= 1.4.4)
sass (3.2.10)
@ -150,10 +195,14 @@ GEM
railties (>= 4.0.0.beta, < 5.0)
sass (>= 3.1.10)
sprockets-rails (~> 2.0.0)
<<<<<<< HEAD
selenium-webdriver (2.34.0)
=======
selenium-webdriver (2.35.1)
>>>>>>> master
childprocess (>= 0.2.5)
multi_json (~> 1.0)
rubyzip
rubyzip (< 1.0.0)
websocket (~> 1.0.4)
simplecov (0.7.1)
multi_json (~> 1.0)
@ -168,22 +217,22 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (~> 2.8)
sqlite3 (1.3.7)
sqlite3 (1.3.8)
swf_fu (2.0.4)
coffee-script
rails (>= 3.1)
therubyracer (0.11.4)
libv8 (~> 3.11.8.12)
therubyracer (0.12.0)
libv8 (~> 3.16.14.0)
ref
thor (0.18.1)
thread_safe (0.1.2)
thread_safe (0.1.3)
atomic
tilt (1.4.1)
timecop (0.6.3)
tolk (1.3.11)
safe_yaml (~> 0.8)
will_paginate
treetop (1.4.14)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
turbolinks (1.3.0)
@ -194,17 +243,21 @@ GEM
rails (>= 3.1)
railties (>= 3.1)
tzinfo (0.3.37)
uglifier (2.1.2)
uglifier (2.2.1)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
uniform_notifier (1.2.0)
uniform_notifier (1.3.0)
websocket (1.0.7)
<<<<<<< HEAD
will_paginate (3.0.4)
will_paginate-bootstrap (0.2.3)
will_paginate (>= 3.0.3)
=======
will_paginate (3.0.5)
>>>>>>> master
xpath (2.0.0)
nokogiri (~> 1.3)
yard (0.8.7)
yard (0.8.7.2)
PLATFORMS
ruby
@ -214,11 +267,12 @@ DEPENDENCIES
aasm
actionpack-xml_parser!
acts_as_list
aruba
aruba!
bcrypt-ruby (~> 3.0.0)
bullet
cache_digests
capybara
codeclimate-test-reporter
coffee-rails (~> 4.0.0)
cucumber-rails
database_cleaner

View file

@ -16,16 +16,29 @@ class DataController < ApplicationController
redirect_to :back
else
@import_to = params[:import_to]
#get column headers and formart as [['name', column_number]...]
i = -1
@headers = import_headers(params[:file].path).collect { |v| [v, i+=1] }
@headers.unshift ['',i]
begin
#get column headers and format as [['name', column_number]...]
i = -1
@headers = import_headers(params[:file].path).collect { |v| [v, i+=1] }
@headers.unshift ['',i]
rescue Exception => e
flash[:error] = "Invalid CVS: could not read headers: #{e}"
redirect_to :back
return
end
#save file for later
directory = "public/uploads/csv"
@path = File.join(directory, params[:file].original_filename)
File.open(@path, "wb") { |f| f.write(params[:file].read) }
begin
uploaded_file = params[:file]
@filename = Tracks::Utils.sanitize_filename(uploaded_file.original_filename)
path_and_file = Rails.root.join('public', 'uploads', 'csv', @filename)
File.open(path_and_file, "wb") { |f| f.write(uploaded_file.read) }
rescue Exception => e
flash[:error] = "Could not save uploaded CSV (#{path_and_file}). Can Tracks write to the upload directory? #{e}"
redirect_to :back
return
end
case @import_to
when 'projects'
@ -44,12 +57,14 @@ class DataController < ApplicationController
def csv_import
begin
filename = Tracks::Utils.sanitize_filename(params[:file])
path_and_file = Rails.root.join('public', 'uploads', 'csv', filename)
case params[:import_to]
when 'projects'
count = Project.import params, current_user
count = Project.import path_and_file, params, current_user
flash[:notice] = "#{count} Projects imported"
when 'todos'
count = Todo.import params, current_user
count = Todo.import path_and_file, params, current_user
flash[:notice] = "#{count} Todos imported"
else
flash[:error] = t('data.invalid_import_destination')
@ -57,11 +72,11 @@ class DataController < ApplicationController
rescue Exception => e
flash[:error] = t('data.invalid_import_destination') + ": #{e}"
end
File.delete(params[:file])
File.delete(path_and_file)
redirect_to import_data_path
end
def import_headers file
def import_headers(file)
CSV.foreach(file, headers: false) do |row|
return row
end
@ -105,13 +120,14 @@ class DataController < ApplicationController
send_data(result, :filename => "tracks_backup.yml", :type => 'text/plain')
end
# export all actions as csv
def csv_actions
content_type = 'text/csv'
CSV::Writer.generate(result = "") do |csv|
CSV.generate(result = "") do |csv|
csv << ["id", "Context", "Project", "Description", "Notes", "Tags",
"Created at", "Due", "Completed at", "User ID", "Show from",
"state"]
current_user.todos.include(:context, :project).all.each do |todo|
current_user.todos.includes(:context, :project, :taggings, :tags).each do |todo|
csv << [todo.id, todo.context.name,
todo.project_id.nil? ? "" : todo.project.name,
todo.description,
@ -127,7 +143,7 @@ class DataController < ApplicationController
send_data(result, :filename => "todos.csv", :type => content_type)
end
# export all notes as csv
def csv_notes
content_type = 'text/csv'
CSV.generate(result = "") do |csv|

View file

@ -0,0 +1,38 @@
require 'openssl'
class MailgunController < ApplicationController
skip_before_filter :login_required, :only => [:mailgun]
before_filter :verify, :only => [:mailgun]
protect_from_forgery with: :null_session
def mailgun
unless params.include? 'body-mime'
Rails.logger.info "Cannot process Mailgun request, no body-mime sent"
render_failure "Unacceptable body-mime", 406
return
end
todo = MessageGateway.receive(params['body-mime'])
if todo
render :xml => todo.to_xml( *todo_xml_params )
else
render_failure "Todo not saved", 406
end
end
private
def verify
unless params['signature'] == OpenSSL::HMAC.hexdigest(
OpenSSL::Digest::Digest.new('sha256'),
SITE_CONFIG['mailgun_api_key'],
'%s%s' % [params['timestamp'], params['token']]
)
Rails.logger.info "Cannot verify Mailgun signature"
render_failure "Access denied", 406
return
end
end
end

View file

@ -137,8 +137,8 @@ class ProjectsController < ApplicationController
limit(current_user.prefs.show_number_completed).
includes(Todo::DEFAULT_INCLUDES) unless @max_completed == 0
@count = @not_done_todos.size
@down_count = @count + @deferred_todos.size + @pending_todos.size
@down_count = @not_done_todos.size + @deferred_todos.size + @pending_todos.size
@count=@down_count
@next_project = current_user.projects.next_from(@project)
@previous_project = current_user.projects.previous_from(@project)
@default_tags = @project.default_tags

View file

@ -87,7 +87,8 @@ class TodosController < ApplicationController
p.parse_dates() unless mobile?
tag_list = p.tag_list
@todo = current_user.todos.build(p.attributes)
@todo = current_user.todos.build
@todo.assign_attributes(p.attributes)
p.add_errors(@todo)
if @todo.errors.empty?
@ -125,6 +126,8 @@ class TodosController < ApplicationController
determine_down_count
@contexts = current_user.contexts
@projects = current_user.projects
@context = @todo.context
@project = @todo.project
@initial_context_name = params['default_context_name']
@initial_project_name = params['default_project_name']
@initial_tags = params['initial_tag_list']
@ -1170,18 +1173,18 @@ end
def update_context
@context_changed = false
if params['todo']['context_id'].blank? && params['context_name'].present?
context = current_user.contexts.where(:name => params['context_name'].strip).first
unless context
@context = current_user.contexts.where(:name => params['context_name'].strip).first
if @context.nil?
@new_context = current_user.contexts.build
@new_context.name = params['context_name'].strip
@new_context.save
@new_context_created = true
@new_container = @new_context
@not_done_todos = [@todo]
context = @new_context
@context = @new_context
end
params["todo"]["context_id"] = context.id
@context_changed = @original_item_context_id != params["todo"]["context_id"] = context.id
params["todo"]["context_id"] = @context.id
@context_changed = @original_item_context_id != params["todo"]["context_id"] = @context.id
end
end

View file

@ -565,6 +565,13 @@ module TodosHelper
return false
end
def should_show_empty_container
source_view do |page|
page.context { return @remaining_in_context == 0 }
end
return @down_count==0
end
def project_container_id(todo)
return "p#{todo.project_id}" unless todo.project.nil?
return "without_project_container"

View file

@ -27,6 +27,7 @@ class MessageGateway < ActionMailer::Base
todo = todo_builder.construct
todo.save!
Rails.logger.info "Saved email as todo for user #{user.login} in context #{context.name}"
todo
end
private
@ -49,10 +50,26 @@ class MessageGateway < ActionMailer::Base
if user.nil?
user = User.where("preferences.sms_email" => address.strip[1.100]).includes(:preference).first
end
if user.present? and !sender_is_in_mailmap?(user,email)
Rails.logger.warn "#{email.from[0]} not found in mailmap for #{user.login}"
return nil
end
Rails.logger.info(!user.nil? ? "Email belongs to #{user.login}" : "User unknown")
return user
end
def sender_is_in_mailmap?(user,email)
if SITE_CONFIG['mailmap'].is_a? Hash and SITE_CONFIG['email_dispatch'] == 'to'
# Look for the sender in the map of allowed senders
SITE_CONFIG['mailmap'][user.preference.sms_email].include? email.from[0]
else
# We can't check the map if it's not defined, or if the lookup is the
# wrong way round, so just allow it
true
end
end
def get_user_from_email_address(email)
SITE_CONFIG['email_dispatch'] == 'single_user' ? get_user_from_env_setting : get_user_from_mail_header(email)
end

View file

@ -138,9 +138,9 @@ class Project < ActiveRecord::Base
@age_in_days ||= ((Time.now.utc - created_at).to_i / 1.day) + 1
end
def self.import(params, user)
def self.import(filename, params, user)
count = 0
CSV.foreach(params[:file], headers: true) do |row|
CSV.foreach(filename, headers: true) do |row|
unless find_by_name_and_user_id row[params[:name].to_i], user.id
project = new
project.name = row[params[:name].to_i]

View file

@ -147,7 +147,7 @@ class RecurringTodo < ActiveRecord::Base
end
def daily_every_x_days=(x)
every_other1 = x if recurring_period=='daily'
self.every_other1 = x if recurring_period=='daily'
end
def daily_every_x_days
@ -489,7 +489,7 @@ class RecurringTodo < ActiveRecord::Base
case self.recurrence_selector
when 0 # specific day of the month
if start.mday > day
if (previous && start.mday >= day) || (previous.nil? && start.mday > day)
# there is no next day n in this month, search in next month
#
# start += n.months

View file

@ -399,11 +399,11 @@ class Todo < ActiveRecord::Base
end
end
def self.import(params, user)
default_context = Context.where(:user_id=>user.id).order('id').first
def self.import(filename, params, user)
default_context = user.contexts.order('id').first
count = 0
CSV.foreach(params[:file], headers: true) do |row|
CSV.foreach(filename, headers: true) do |row|
unless find_by_description_and_user_id row[params[:description].to_i], user.id
todo = new
todo.user = user

View file

@ -5,7 +5,7 @@
<%= select_tag(l, options_for_select(@headers) ) %>
<br>
<% end %>
<%= hidden_field_tag :file, @path %>
<%= hidden_field_tag :file, @filename %>
<%= hidden_field_tag :import_to, @import_to %>
<%= submit_tag "Import" %>
<% end %>

View file

@ -42,7 +42,7 @@ function remove_todo(next_steps) {
function add_to_existing_container(next_steps) {
$('#<%= item_container_id(@todo) %>_items').append(html_for_todo());
<% if source_view_is_one_of(:project,:calendar) -%>
<% if source_view_is_one_of(:calendar) -%>
next_steps.go();
<% if (@target_context_count==1) || ( (@todo.deferred? || @todo.pending?) && @remaining_deferred_or_pending_count == 1) -%>
$("#<%= empty_container_msg_div_id %>").slideUp(100);
@ -83,7 +83,7 @@ function highlight_updated_todo(next_steps) {
}
function update_empty_container(next_steps) {
<% if @down_count==0 -%>
<% if should_show_empty_container -%>
$('div#no_todos_in_view').slideDown(400, function(){ next_steps.go(); });
<% else -%>
$('div#no_todos_in_view').fadeOut(100, function(){ next_steps.go(); });

View file

@ -3,6 +3,8 @@ Tracksapp::Application.routes.draw do
root :to => 'todos#index'
post 'mailgun/mime' => 'mailgun#mailgun'
post 'login' => 'login#login'
get 'login' => 'login#login'
get 'login/check_expiry' => 'login#check_expiry'

View file

@ -58,7 +58,20 @@ open_signups: false
# (see http://docs.cloudmailin.com/validating_the_sender)
# cloudmailin: asdasd
# Mailgun api key - used to verify incoming HTTP requests from Mailgun.org
# mailgun_api_key: key-abcdef1234567890
# change this to reflect the email address of the admin that you want to show
# on the signup page
admin_email: my.email@domain.com
admin_email: my.email@domain.com
# Map of allowed incoming email addresses to real users
# Requires email_dispatch == 'to'
# This allows you to specify _who_ can send email Todos to your list
# The format is your incoming email (as per preferences page) for the key, and
# an array-list of acceptable senders for that account
#mailmap:
# 'user@preferences.from.value':
# - 'acceptable1@personal.org'
# - 'acceptable2@work.com'

View file

@ -0,0 +1,63 @@
require 'net/https'
require File.expand_path(File.dirname(__FILE__) + '/tracks_xml_builder')
module TracksCli
class TracksAPI
def initialize(options)
@options = options
end
def get_http(uri)
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == "https" # enable SSL/TLS
http.use_ssl = true
http.ca_path = "/etc/ssl/certs/" # Debian based path
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.verify_depth = 5
end
http
end
def context_uri_for(context_id)
URI.parse(@options[:context_prefix] + context_id.to_s + ".xml")
end
def todo_uri
URI.parse(@options[:uri])
end
def project_uri
URI.parse(@options[:projects_uri])
end
def post(uri, body)
req = Net::HTTP::Post.new(uri.path, "Content-Type" => "text/xml")
req.basic_auth @options[:login], @options[:password]
req.body = body
get_http(uri).request(req)
end
def get(uri)
req = Net::HTTP::Get.new(uri.path)
req.basic_auth @options[:login], @options[:password]
get_http(uri).request(req)
end
def post_todo(todo)
post(todo_uri, TracksXmlBuilder.new.build_todo_xml(todo))
end
def post_project(project)
post(project_uri, TracksXmlBuilder.new.build_project_xml(project))
end
def get_context(context_id)
get(context_uri_for(context_id))
end
end
end

View file

@ -0,0 +1,66 @@
require 'active_support/time_with_zone'
module TracksCli
class TracksXmlBuilder
def xml_for_description(description)
"<description>#{description}</description>"
end
def xml_for_project_id(project_id)
"<project_id>#{project_id}</project_id>"
end
def xml_for_show_from(show_from)
show_from.nil? ? "" : "<show-from type=\"datetime\">#{Time.at(show_from).xmlschema}</show-from>"
end
def xml_for_notes(notes)
notes.nil? ? "" : "<notes>#{notes}</notes>"
end
def xml_for_taglist(taglist)
unless taglist.nil?
tags = taglist.split(",")
if tags.length() > 0
tags = tags.collect { |tag| "<tag><name>#{tag.strip}</name></tag>" unless tag.strip.empty?}.join('')
return "<tags>#{tags}</tags>"
end
else
return ""
end
end
def xml_for_context(context_name, context_id)
if context_name && !context_name.empty?
return "<context><name>#{context_name}</name></context>"
else
return "<context_id>#{context_id}</context_id>"
end
end
def xml_for_predecessor(dependend, predecessor)
dependend ? "<predecessor_dependencies><predecessor>#{predecessor}</predecessor></predecessor_dependencies>" : ""
end
def build_todo_xml(todo)
props = [
xml_for_description(todo[:description]),
xml_for_project_id(todo[:project_id]),
xml_for_show_from(todo[:show_from]),
xml_for_notes(todo[:notes]),
xml_for_taglist(todo[:taglist]),
xml_for_context(todo[:context_name], todo[:context_id]),
xml_for_predecessor(todo[:is_dependend], todo[:predecessor])
]
"<todo>#{props.join("")}</todo>"
end
def build_project_xml(project)
"<project><name>#{project[:description]}</name><default-context-id>#{project[:default_context_id]}</default-context-id></project>"
end
end
end

View file

@ -1,138 +1,109 @@
#!/usr/bin/env ruby
#
# Author: Vitalie Lazu <vitalie.lazu@gmail.com>
# Date: Sat, 10 Jan 2009 19:12:43 +0200
#
# CLI ruby client for Tracks: rails application for GTD methodology
# CLI ruby client for Tracks: rails application for GTD methodology (First author: Vitalie Lazu <vitalie.lazu@gmail.com>)
# http://www.getontracks.org/development/
# You need to set ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
# and set GTD_TODOS_URL to your tracks install. It defaults to 'http://localhost:3000/todos.xml'
#
# Example:
# $ echo "todo 1\ntodo2" | GTD_LOGIN=username GTD_PASSWORD=secret ruby tracks_cli_client.rb -c 123 -p 456
# This will post todo 1 and todo 2 to localhost:3000 using username and secret als credentials
require 'net/https'
require 'optparse'
require 'cgi'
require 'time'
require File.expand_path(File.dirname(__FILE__) + '/tracks_cli/tracks_api')
class Hash
def to_query_string
map { |k, v|
if v.instance_of?(Hash)
v.map { |sk, sv|
"#{k}[#{sk}]=#{sv}"
}.join('&')
else
"#{k}=#{v}"
end
}.join('&')
class PostLineAsTodo
def initialize(options)
@options = options
@tracks = TracksCli::TracksAPI.new(
uri: ENV['GTD_TODOS_URL'] || 'http://localhost:3000/todos.xml',
login: ENV['GTD_LOGIN'],
password: ENV['GTD_PASSWORD'])
@context_id = options[:context_id] ? options[:context_id].to_i : 1
@project_id = options[:project_id] ? options[:project_id].to_i : 1
end
def post(lines)
lines.each_line do |l|
l.chomp!
next if l.strip.empty?
resp = @tracks.post_todo(
description: CGI.escapeHTML(l),
context_id: @context_id,
project_id: @project_id,
show_from: @options[:show_from])
if resp.code == '302' || resp.code == '201'
puts resp['location']
else
p resp.body
raise Error
end
end
end
end
module Gtd
class API
GTD_URI = ENV['GTD_TODOS_URL'] || 'http://localhost:3000/todos.xml'
class Error < StandardError; end
class InvalidParser < StandardError; end
def post(lines, options = {})
uri = URI.parse(GTD_URI)
http = Net::HTTP.new(uri.host, uri.port)
class ConsoleOptions
attr_reader :parser, :options
if uri.scheme == "https" # enable SSL/TLS
http.use_ssl = true
http.ca_path = "/etc/ssl/certs/" # Debian based path
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.verify_depth = 5
def initialize
@options = {}
@parser = OptionParser.new do |cmd|
cmd.banner = "Ruby Gtd CLI - takes todos input from STDIN"
cmd.separator ''
cmd.on('-h', '--help', 'Displays this help message') do
puts @parser
exit
end
lines.each_line do |l|
l.chomp!
next if l.strip.empty?
cmd.on('-p [N]', Integer, "project id to set for new todo") do |v|
@options[:project_id] = v
end
description = CGI.escapeHTML(l)
context_id = options[:context_id] ? options[:context_id].to_i : 1
project_id = options[:project_id] ? options[:project_id].to_i : 1
props = "<description>#{description}</description><project_id>#{project_id}</project_id><context_id>#{context_id}</context_id>"
cmd.on('-c [N]', Integer, 'context id to set') do |v|
@options[:context_id] = v
end
if options[:show_from]
props << "<show-from type=\"datetime\">#{Time.at(options[:show_from]).xmlschema}</show-from>"
end
cmd.on('-w [N]', Integer, 'Postpone task for N weeks') do |v|
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * (v || 1)
end
req = Net::HTTP::Post.new(uri.path, "Content-Type" => "text/xml")
req.basic_auth ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
req.body = "<todo>#{props}</todo>"
resp = http.request(req)
if resp.code == '302' || resp.code == '201'
puts resp['location']
else
p resp.body
raise Gtd::Error
end
cmd.on('-m [N]', Integer, 'Postpone task for N months') do |v|
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * 4 * (v || 1)
end
end
end
class Error < StandardError; end
class InvalidParser < StandardError; end
def run(args)
@parser.parse!(args)
lines = STDIN.read
class ConsoleOptions
attr_reader :parser, :options
def initialize
@options = {}
@parser = OptionParser.new do |cmd|
cmd.banner = "Ruby Gtd CLI - takes todos input from STDIN"
cmd.separator ''
cmd.on('-h', '--help', 'Displays this help message') do
puts @parser
exit
end
cmd.on('-p [N]', Integer, "project id to set for new todo") do |v|
@options[:project_id] = v
end
cmd.on('-c [N]', Integer, 'context id to set') do |v|
@options[:context_id] = v
end
cmd.on('-w [N]', Integer, 'Postpone task for N weeks') do |v|
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * (v || 1)
end
cmd.on('-m [N]', Integer, 'Postpone task for N months') do |v|
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * 4 * (v || 1)
end
end
end
def run(args)
@parser.parse!(args)
lines = STDIN.read
if lines.strip.empty?
puts "Please pipe in some content to tracks on STDIN."
exit 1
end
gtd = API.new
gtd.post(lines, @options)
exit 0
rescue InvalidParser
puts "Please specify a valid format parser."
exit 1
rescue Error
puts "An unknown error occurred"
if lines.strip.empty?
puts "Please pipe in some content to tracks on STDIN."
exit 1
end
PostLineAsTodo.new(@options).post(lines)
exit 0
rescue InvalidParser
puts "Please specify a valid format parser."
exit 1
rescue Error
puts "An unknown error occurred"
exit 1
end
end
if $0 == __FILE__
app = Gtd::ConsoleOptions.new
app.run(ARGV)
ConsoleOptions.new.run(ARGV)
end

486
doc/tracks_template_cli.rb Executable file → Normal file
View file

@ -1,7 +1,5 @@
#!/usr/bin/env ruby
# Version 0.4 (Dec 17, 2011)
#
# Based on the tracks_cli by Vitalie Lazu (https://gist.github.com/45537)
#
@ -67,313 +65,247 @@
# .Set a reminder to check for reimbursement for [location]
# .Mail folder to secretary
# Instantiate this template: ./tracks_template_cli -c 1 -f template_file.txt
# Instantiate this template: ruby tracks_template_cli -c 1 -f template_file.txt
require 'net/https'
require 'optparse'
require 'cgi'
require 'time'
require 'readline'
require File.expand_path(File.dirname(__FILE__) + '/tracks_cli/tracks_api')
class Hash
def to_query_string
map { |k, v|
if v.instance_of?(Hash)
v.map { |sk, sv|
"#{k}[#{sk}]=#{sv}"
}.join('&')
else
"#{k}=#{v}"
end
}.join('&')
class TemplateParser
def initialize
@keywords = {}
end
def parse_keyword(token)
print "Input required for "+token+": "
@keywords[token]=gets.chomp
end
def replace_tokens_in(line)
@keywords.each{ |key,val| line=line.sub(key,val) }
line
end
def parse_todo(line)
options = {}
# first char is . or ^ the latter meaning this todo is dependent on the previous one
options[:depend]= line[0].chr == "^" ? true : false;
line = line[1..line.length] # remove first char
tmp= line.split("|")
if tmp.length > 5
puts "Formatting error: found too many |"
exit 1
end
tmp[0].chomp!
options[:description]=tmp[0]
tmp.each_with_index do |t,idx|
t=t.strip.chomp
t=nil if t.empty?
tmp[idx]=t
end
options[:context]=tmp[1]
options[:taglist]=tmp[2]
options[:notes] =tmp[3]
options
end
def parse(file, poster)
while line = file.gets
line = line.strip
# skip line if empty or comment
next if (line.empty? || line[0].chr == "#")
# check if line defines a token
if (line.split(' ')[0] == "token")
parse_keyword line.split(' ')[1]
next
end
# replace defined tokes in current line
line = replace_tokens_in line
# line is either todo/dependency or project
if (line[0].chr == "." ) || (line[0].chr == "^")
if @last_project_id.nil?
puts "Warning: no project specified for task \"#{line}\". Using default project."
end
poster.postTodo(parse_todo(line), @last_project_id)
else
@last_project_id = poster.postProject(line)
end
end
end
end
module Gtd
class API
GTD_URI_TODOS = ENV['GTD_TODOS_URL'] || 'http://localhost:3000/todos.xml'
GTD_URI_PROJECTS = ENV['GTD_PROJECTS_URL'] || 'http://localhost:3000/projects.xml'
GTD_URI_CONTEXTS_PREFIX = ENV['GTD_CONTEXT_URL_PREFIX'] || 'http://localhost:3000/contexts/'
GTD_URI_CONTEXTS = ENV['GTD_CONTEXT_URL'] || 'http://localhost:3000/contexts.xml'
def postTodo(l, options = {})
uri = URI.parse(GTD_URI_TODOS)
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == "https" # enable SSL/TLS
http.use_ssl = true
http.ca_path = "/etc/ssl/certs/" # Debian based path
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.verify_depth = 5
end
l.chomp!
description = CGI.escapeHTML(l)
context_id = options[:context_id] ? options[:context_id].to_i : 1
project_id = options[:project_id] ? options[:project_id].to_i : 1
props = "<description>#{description}</description><project_id>#{project_id}</project_id>"
if options[:show_from]
props << "<show-from type=\"datetime\">#{Time.at(options[:show_from]).xmlschema}</show-from>"
end
if options[:note]
props << "<notes>#{options[:note]}</notes>"
end
if options[:taglist]
tags = options[:taglist].split(",")
if tags.length() > 0
tags = tags.collect { |tag| "<tag><name>#{tag.strip}</name></tag>" unless tag.strip.empty?}.join('')
props << "<tags>#{tags}</tags>"
end
end
if not (options[:context].nil? || options[:context].empty?)
props << "<context><name>#{options[:context]}</name></context>"
else
## use the default context
props << "<context_id>#{context_id}</context_id>"
end
if options[:depend]
props << "<predecessor_dependencies><predecessor>#{options[:last_todo_id]}</predecessor></predecessor_dependencies>"
end
req = Net::HTTP::Post.new(uri.path, "Content-Type" => "text/xml")
req.basic_auth ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
req.body = "<todo>#{props}</todo>"
puts req.body if options[:verbose]
resp = http.request(req)
if resp.code == '302' || resp.code == '201'
puts resp['location'] if options[:verbose]
# return the todo id
return resp['location'].split("/").last
else
p resp.body
raise Gtd::Error
end
end
def postProject(l, options = {})
uri = URI.parse(GTD_URI_PROJECTS)
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == "https" # enable SSL/TLS
http.use_ssl = true
http.ca_path = "/etc/ssl/certs/" # Debian based path
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.verify_depth = 5
end
l.chomp!
description = CGI.escapeHTML(l)
props = "<name>#{l}</name><default-context-id>#{options[:context_id]}</default-context-id>"
req = Net::HTTP::Post.new(uri.path, "Content-Type" => "text/xml")
req.basic_auth ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
req.body = "<project>#{props}</project>"
resp = http.request(req)
if resp.code == '302' || resp.code == '201'
puts resp['location'] if options[:verbose]
# return the project id
return resp['location'].split("/").last
else
p resp.body
raise Gtd::Error
end
end
def queryContext(contextID)
return false unless contextID.is_a? Integer
uri = URI.parse(GTD_URI_CONTEXTS_PREFIX + contextID.to_s + ".xml")
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == "https" # enable SSL/TLS
http.use_ssl = true
http.ca_path = "/etc/ssl/certs/" # Debian based path
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.verify_depth = 5
end
req = Net::HTTP::Get.new(uri.path)
req.basic_auth ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
resp = http.request(req)
case resp
when Net::HTTPSuccess
return true
else
return false
end
end
class TemplatePoster
def initialize(options)
@options = options
@tracks = TracksCli::TracksAPI.new({
uri: ENV['GTD_TODOS_URL'] || 'http://localhost:3000/todos.xml',
login: ENV['GTD_LOGIN'],
password: ENV['GTD_PASSWORD'],
projects_uri: ENV['GTD_PROJECTS_URL'] || 'http://localhost:3000/projects.xml',
contexts_uri: ENV['GTD_CONTEXT_URL'] || 'http://localhost:3000/contexts.xml',
context_prefix: ENV['GTD_CONTEXT_URL_PREFIX'] || 'http://localhost:3000/contexts/'})
@context_id = options[:context_id] ? options[:context_id].to_i : 1
@project_id = options[:project_id] ? options[:project_id].to_i : 1
end
def postTodo(parsed_todo, project_id)
resp = @tracks.post_todo(
description: CGI.escapeHTML(parsed_todo[:description]),
context_name: parsed_todo[:context],
context_id: @context_id,
project_id: project_id || @project_id,
show_from: parsed_todo[:show_from],
notes: parsed_todo[:notes],
is_dependend: parsed_todo[:depend],
predecessor: @last_posted_todo_id)
class Error < StandardError; end
class InvalidParser < StandardError; end
if resp.code == '302' || resp.code == '201'
puts resp['location'] if @options[:verbose]
# return the todo id
@last_posted_todo_id = resp['location'].split("/").last
return @last_posted_todo_id
else
p resp.body
raise Error
end
end
class ConsoleOptions
attr_reader :parser, :options, :keywords
def postProject(project_description)
project_description.chomp!
def initialize
@options = {}
@keywords = {}
resp = @tracks.post_project(
description: CGI.escapeHTML(project_description),
default_context_id: @context_id)
@parser = OptionParser.new do |cmd|
cmd.banner = "Ruby Gtd Templates CLI"
if resp.code == '302' || resp.code == '201'
puts resp['location'] if @options[:verbose]
cmd.separator ''
# return the project id
return resp['location'].split("/").last
else
p resp.body
raise Error
end
end
cmd.on('-h', '--help', 'Displays this help message') do
puts @parser
exit
end
def queryContext(context_id)
return false unless context_id.is_a? Integer
cmd.on('-p [N]', Integer, "project id to set for new todo") do |v|
@options[:project_id] = v
end
resp = @tracks.get_context(context_id)
cmd.on('-k [S]', "keyword to be replaced") do |v|
@keywords[v.split("=")[0]] = v.split("=")[1]
end
return resp.code == '200'
end
cmd.on('-v', "verbose on") do |v|
@options[:verbose] = true
end
end
cmd.on('-f [S]', "filename of the template") do |v|
@filename = v
end
class Error < StandardError; end
class InvalidParser < StandardError; end
cmd.on('-c [N]', Integer, 'default context id to set for new projects') do |v|
@options[:context_id] = v
end
class ConsoleOptions
attr_reader :parser, :options, :keywords
cmd.on('-w [N]', Integer, 'Postpone task for N weeks') do |v|
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * (v || 1)
end
def initialize
@options = {}
@keywords = {}
cmd.on('-m [N]', Integer, 'Postpone task for N months') do |v|
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * 4 * (v || 1)
end
@parser = OptionParser.new do |cmd|
cmd.banner = "Ruby Gtd Templates CLI"
cmd.separator ''
cmd.on('-h', '--help', 'Displays this help message') do
puts @parser
exit
end
cmd.on('-p [N]', Integer, "project id to set for new todo") do |v|
@options[:project_id] = v
end
cmd.on('-k [S]', "keyword to be replaced") do |v|
@keywords[v.split("=")[0]] = v.split("=")[1]
end
cmd.on('-v', "verbose on") do |v|
@options[:verbose] = true
end
cmd.on('-f [S]', "filename of the template") do |v|
@filename = v
end
cmd.on('-c [N]', Integer, 'default context id to set for new projects') do |v|
@options[:context_id] = v
end
cmd.on('-w [N]', Integer, 'Postpone task for N weeks') do |v|
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * (v || 1)
end
cmd.on('-m [N]', Integer, 'Postpone task for N months') do |v|
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * 4 * (v || 1)
end
end
end
def run(args)
@parser.parse!(args)
# lines = STDIN.read
gtd = API.new
def run(args)
@parser.parse!(args)
@poster = TemplatePoster.new(@options)
if @filename != nil and not File.exist?(@filename)
puts "ERROR: file #{@filename} doesn't exist"
exit 1
end
if ENV['GTD_LOGIN'] == nil
puts "ERROR: no GTD_LOGIN environment variable set"
exit 1
end
if ENV['GTD_PASSWORD'] == nil
puts "ERROR: no GTD_PASSWORD environment variable set"
exit 1
end
if @filename == nil
file = STDIN
else
file = File.open(@filename)
end
## check for existence of the context
if !@options[:context_id]
puts "ERROR: need to specify a context_id with -c option. Go here to find one: #{API::GTD_URI_CONTEXTS}"
exit 1
end
if !gtd.queryContext(@options[:context_id])
puts "Error: context_id #{options[:context_id]} doesn't exist"
exit 1
end
#lines.each_line do |line|
while line = file.gets
line = line.strip
next if (line.empty? || line[0].chr == "#")
if (line.split(' ')[0] == "token")
## defining a new token; ask for input
newtok=line.split(' ')[1]
print "Input required for "+newtok+": "
@keywords[newtok]=gets.chomp
next
end
# replace tokens
@keywords.each do |key,val|
line=line.sub(key,val)
end
# decide whether project or task
if (line[0].chr == "." ) || (line[0].chr == "^")
@options[:depend]= line[0].chr == "^" ? true : false;
line = line[1..line.length]
# find notes
tmp= line.split("|")
if tmp.length > 5
puts "Formatting error: found too many |"
exit 1
end
line=tmp[0]
tmp.each_with_index do |t,idx|
t=t.strip.chomp
t=nil if t.empty?
tmp[idx]=t
end
@options[:context]=tmp[1]
@options[:taglist]=tmp[2]
@options[:note]=tmp[3]
if !@options[:project_id]
puts "Warning: no project specified for task \"#{line}\". Using default project."
end
@options[:last_todo_id]=gtd.postTodo(line, @options)
else
@options[:project_id]=gtd.postProject(line, @options)
end
end
exit 0
rescue InvalidParser
puts "Please specify a valid format parser."
exit 1
rescue Error
puts "An unknown error occurred"
if !@filename.nil? and not File.exist?(@filename)
puts "ERROR: file #{@filename} doesn't exist"
exit 1
end
if ENV['GTD_LOGIN'] == nil
puts "ERROR: no GTD_LOGIN environment variable set"
exit 1
end
if ENV['GTD_PASSWORD'] == nil
puts "ERROR: no GTD_PASSWORD environment variable set"
exit 1
end
file = @filename.nil? ? STDIN : File.open(@filename)
## check for existence of the context
if @options[:context_id].nil?
puts "ERROR: need to specify a context_id with -c option."
exit 1
end
if !@poster.queryContext(@options[:context_id])
puts "Error: context_id #{options[:context_id]} doesn't exist"
exit 1
end
TemplateParser.new.parse(file, @poster)
exit 0
rescue InvalidParser
puts "Please specify a valid format parser."
exit 1
rescue Error
puts "An unknown error occurred"
exit 1
end
end
if $0 == __FILE__
app = Gtd::ConsoleOptions.new
app.run(ARGV)
ConsoleOptions.new.run(ARGV)
end

View file

@ -0,0 +1,36 @@
Feature: Add a todo to Tracks on console
In order to be able to add a todo from the command line
As a user who has installed Tracks with console access
I want to run the script to add a todo
These scenarios are tagged javascript so that there is a Tracks server running
to use from the command line script
Background:
Given the following user records
| login | password | is_admin |
| testuser | secret | false |
| admin | secret | true |
And I have logged in as "testuser" with password "secret"
And I have a context called "Context A"
And I have a project called "Project A"
@javascript @aruba
Scenario: Create a single todo
Given a console input that looks like
"""
a new todo
"""
When I execute the add-todo script
Then I should have 1 todo in project "Project A"
@javascript @aruba
Scenario: Create multiple todos
Given a console input that looks like
"""
todo 1
todo 2
"""
When I execute the add-todo script
Then I should have 2 todo in project "Project A"

View file

@ -12,17 +12,16 @@ Feature: Edit a context
And I have a project called "test project"
And I have 2 todos in project "test project" in context "@pc" with tags "starred" prefixed by "test_project "
@javascript
@javascript
Scenario: In place edit of context name
Given I have a context called "Errands"
When I go to the context page for "Errands"
When I go to the context page for "@pc"
And I edit the context name in place to be "OutAndAbout"
Then I should see the context name is "OutAndAbout"
When I go to the contexts page
Then I should see that a context named "Errands" is not present
And I should see that a context named "OutAndAbout" is present
@javascript
@javascript
Scenario: Editing the context of a todo will remove the todo
When I go to the the context page for "@pc"
Then the badge should show 2
@ -37,7 +36,7 @@ Feature: Edit a context
Then I should not see the todo "test_project todo 1"
And I should see "changed"
@javascript
@javascript
Scenario: Editing the context of the last todo will remove the todo and show empty message
When I go to the the context page for "@pc"
And I edit the context of "test_project todo 1" to "@laptop"

View file

@ -1,4 +1,4 @@
Feature: Create project from template
Feature: Create project from template on console
In order to be able to create a project from a template
As a user who has installed Tracks with console access
I want to run the script to add projects and actions from a template
@ -21,7 +21,7 @@ Feature: Create project from template
My first project
.My first task in this project
"""
When I execute the script
When I execute the template script
Then I should have a project called "My first project"
And I should have 1 todo in project "My first project"
@ -33,5 +33,5 @@ Feature: Create project from template
.Todo
^Dependent
"""
When I execute the script
When I execute the template script
Then the successors of "Todo" should include "Dependent"

View file

@ -0,0 +1,59 @@
Given /^a template that looks like$/ do |template|
steps %{
Given a file named "template.txt" with:
"""
#{template}
"""
}
end
Given /^a console input that looks like$/ do |input|
steps %{
Given a file named "todo.txt" with:
"""
#{input}
"""
}
end
When /^I execute the template script$/ do
step "I cd to \"../..\""
context_id = @current_user.contexts.first.id
port = Capybara.current_session.server.port
# assumes there is a context with id=1
cli = "ruby doc/tracks_template_cli.rb -c #{context_id} -f tmp/aruba/template.txt"
set_env('GTD_LOGIN','testuser')
set_env('GTD_PASSWORD', 'secret')
set_env('GTD_TODOS_URL', "http://localhost:#{port}/todos.xml")
set_env('GTD_PROJECTS_URL', "http://localhost:#{port}/projects.xml")
set_env('GTD_CONTEXT_URL_PREFIX', "http://localhost:#{port}/contexts/")
set_env("GTD_CONTEXT_URL","http://localhost:#{port}/contexts.xml")
step "I run `#{cli}`"
end
When /^I execute the add-todo script$/ do
step "I cd to \"../..\""
# assumes there is a context and a project
context_id = @current_user.contexts.first.id
project_id = @current_user.projects.first.id
port = Capybara.current_session.server.port
cli = "ruby doc/tracks_cli_client.rb -c #{context_id} -p #{project_id}"
set_env('GTD_LOGIN','testuser')
set_env('GTD_PASSWORD', 'secret')
set_env('GTD_TODOS_URL', "http://localhost:#{port}/todos.xml")
step "I run `#{cli}` interactively"
step "I pipe in the file \"tmp/aruba/todo.txt\""
# it seems aruba does not wait for process to end with interactively run command, but
# continues anyway which will start cleaning up the database while the process is still running
# so wait 2 secs for the process to finish
sleep 2
end

View file

@ -1,27 +0,0 @@
Given /^a template that looks like$/ do |template|
steps %{
Given a file named "template.txt" with:
"""
#{template}
"""
}
end
When /^I execute the script$/ do
step "I cd to \"../..\""
context_id = @current_user.contexts.first.id
port = Capybara.current_session.server.port
# assumes there is a context with id=1
cli = "ruby doc/tracks_template_cli.rb -c #{context_id} -f tmp/aruba/template.txt"
set_env('GTD_LOGIN','testuser')
set_env('GTD_PASSWORD', 'secret')
set_env('GTD_TODOS_URL', "http://localhost:#{port}/todos.xml")
set_env('GTD_PROJECTS_URL', "http://localhost:#{port}/projects.xml")
set_env('GTD_CONTEXT_URL_PREFIX', "http://localhost:#{port}/contexts/")
set_env("GTD_CONTEXT_URL","http://localhost:#{port}/contexts.xml")
step "I run `#{cli}`"
end

View file

@ -45,7 +45,7 @@ end
#
# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do
# # { :except => [:widgets] } may not do what you expect here
# # as tCucumber::Rails::Database.javascript_strategy overrides
# # as Cucumber::Rails::Database.javascript_strategy overrides
# # this setting.
# DatabaseCleaner.strategy = :truncation
# end

View file

@ -1,6 +1,6 @@
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
# It is recommended to regenerate this file in the future when you upgrade to a
# newer version of cucumber-rails. Consider adding your own code to a new file
# It is recommended to regenerate this file in the future when you upgrade to a
# newer version of cucumber-rails. Consider adding your own code to a new file
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
# files.
@ -14,19 +14,19 @@ begin
require 'cucumber/rake/task'
namespace :cucumber do
Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
Cucumber::Rake::Task.new({:ok => 'test:prepare'}, 'Run features that should pass') do |t|
t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
t.fork = true # You may get faster startup if you set this to false
t.profile = 'default'
end
Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
Cucumber::Rake::Task.new({:wip => 'test:prepare'}, 'Run features that are being worked on') do |t|
t.binary = vendored_cucumber_bin
t.fork = true # You may get faster startup if you set this to false
t.profile = 'wip'
end
Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
Cucumber::Rake::Task.new({:rerun => 'test:prepare'}, 'Record failing features and run only them if any exist') do |t|
t.binary = vendored_cucumber_bin
t.fork = true # You may get faster startup if you set this to false
t.profile = 'rerun'
@ -50,8 +50,8 @@ begin
STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
end
# In case we don't have ActiveRecord, append a no-op task that we can depend upon.
task 'db:test:prepare' do
# In case we don't have the generic Rails test:prepare hook, append a no-op task that we can depend upon.
task 'test:prepare' do
end
task :stats => 'cucumber:statsetup'

View file

@ -38,6 +38,10 @@ module Tracks
RedCloth.new(text).to_html
end
def self.sanitize_filename(filename)
filename.gsub(/[^0-9A-z.\-]/, '_')
end
private
def self.helpers

View file

@ -13,6 +13,7 @@ class IntegrationsControllerTest < ActionController::TestCase
def test_cloudmailin_integration_success
SITE_CONFIG['cloudmailin'] = "123456789"
SITE_CONFIG['email_dispatch'] = 'from'
post :cloudmailin, {
"html"=>"",
"plain"=>"asdasd",

View file

@ -0,0 +1,66 @@
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
class MailgunControllerTest < ActionController::TestCase
def setup
@user = users(:sms_user)
@inbox = contexts(:inbox)
end
def load_message(filename)
File.read(File.join(Rails.root, 'test', 'fixtures', filename))
end
def test_mailgun_signature_verifies
SITE_CONFIG['mailgun_api_key'] = "123456789"
SITE_CONFIG['email_dispatch'] = 'from'
post :mailgun, {
"timestamp" => "1379539674",
"token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5",
"signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1",
"body-mime" => load_message('mailgun_message1.txt')
}
assert_response :success
end
def test_mailgun_creates_todo_with_mailmap
SITE_CONFIG['mailgun_api_key'] = "123456789"
SITE_CONFIG['email_dispatch'] = 'to'
SITE_CONFIG['mailmap'] = {
'5555555555@tmomail.net' => ['incoming@othermail.com', 'notused@foo.org']
}
todo_count = Todo.count
post :mailgun, {
"timestamp" => "1379539674",
"token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5",
"signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1",
"body-mime" => load_message('mailgun_message2.txt')
}
assert_response :success
assert_equal(todo_count+1, Todo.count)
message_todo = Todo.where(:description => "test").first
assert_not_nil(message_todo)
assert_equal(@inbox, message_todo.context)
assert_equal(@user, message_todo.user)
end
def test_mailgun_signature_fails
SITE_CONFIG['mailgun_api_key'] = "invalidkey"
SITE_CONFIG['email_dispatch'] = 'from'
post :mailgun, {
"timestamp" => "1379539674",
"token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5",
"signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1",
"body-mime" => load_message('mailgun_message1.txt')
}
assert_response 406
end
end

View file

@ -729,23 +729,11 @@ class TodosControllerTest < ActionController::TestCase
recurring_todo_1.every_other2 = 1
assert recurring_todo_1.save
# mark todo_1 as complete by toggle_check, this gets rid of todo_1 that was
# not correctly created from the adjusted recurring pattern we defined
# above.
# mark todo_1 as complete by toggle_check
xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
todo_1.reload
assert todo_1.completed?
# locate the new todo. This todo is created from the adjusted recurring
# pattern defined in this test
new_todo = Todo.where(:recurring_todo_id => recurring_todo_1.id, :state => 'active').first
assert !new_todo.nil?
# mark new_todo as complete by toggle_check
xhr :post, :toggle_check, :id => new_todo.id, :_source_view => 'todo'
new_todo.reload
assert todo_1.completed?
# locate the new todo in tickler
new_todo = Todo.where(:recurring_todo_id => recurring_todo_1.id, :state => 'deferred').first
assert !new_todo.nil?
@ -799,6 +787,44 @@ class TodosControllerTest < ActionController::TestCase
assert next_todo.due > todo.due
end
def test_check_for_next_todo_monthly
login_as :admin_user
tomorrow = Time.zone.now + 1.day
# Given a monthly repeat pattern
recurring_todo = RecurringTodo.find(5)
recurring_todo.target = "due_date"
recurring_todo.recurring_period = "monthly"
recurring_todo.every_other1 = tomorrow.day
recurring_todo.every_other2 = 1
recurring_todo.save
# Given a recurring todo (todo) that belongs to the repeat pattern (recurring_todo) and is due tomorrow
todo = Todo.where(:recurring_todo_id => 1).first
assert todo.from_recurring_todo?
todo.recurring_todo_id = 5 # rewire todo to the repeat pattern above
todo.due = tomorrow
todo.save!
# When I mark the todo complete
xhr :post, :toggle_check, :id => todo.id, :_source_view => 'todo'
todo.reload
assert todo.completed?
# Then there should not be an active todo beloning to the repeat pattern
next_todo = Todo.where(:recurring_todo_id => recurring_todo.id, :state => 'active').first
assert next_todo.nil?
# Then there should be one new deferred todo
next_todo = Todo.where(:recurring_todo_id => recurring_todo.id, :state => 'deferred').first
assert !next_todo.nil?
assert !next_todo.show_from.nil?
# check that the due date of the new todo is later than tomorrow
assert next_todo.due > todo.due
end
############
# todo notes
############

27
test/fixtures/mailgun_message1.txt vendored Normal file
View file

@ -0,0 +1,27 @@
Received: by luna.mailgun.net with SMTP mgrt 8745468409521; Wed, 18 Sep 2013\n 20:51:11 +0000
X-Envelope-From: <5555555555@tmomail.net>
Received: from mail-pa0-f45.google.com (mail-pa0-f45.google.com\n [209.85.220.45]) by mxa.mailgun.org with ESMTP id\n 523a123d.7f3a16482330-in1; Wed, 18 Sep 2013 20:51:09 -0000 (UTC)
Received: by mail-pa0-f45.google.com with SMTP id bg4so8745646pad.18 for\n <test@test.mailgun.org>; Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com;\n s=20120113; h=mime-version:date:message-id:subject:from:to:content-type;\n bh=XwBMHRr9XT/f5w7Vzap//yQJ9VoVaVcjEQ5+xjgSoMk=;\n b=IsSDTe2jY8XucXOxviOrGg1upSm5IOddtOsHFpgcyND4F9glSIU+xixRK60/9Dbqae\n BT5On3C+Aoffpj/WUqnp1+hA89JYDbWhzXcJPhkW8FcFnPvAzQ0G/loV7+CafWLubEnp\n T61owZQAQgdkHsrZMf1ChmpghNMJuDLMJvYmiijIId+z7arBocjErhHqwRY4Y6lmFdTo\n s9uGtVJ7Av1g7m8KyGZY6kzh/XPhbr4B0tjIFpyp+bDFH2Nk290RAB/2garNBfQAPhzy\n hIvvbuz5MLFWSnW17eXdymHAEH6oSbRfar8ocxcY5T+hg++nfsegUJ6sPRG1G63qnsj4 dCig==
Mime-Version: 1.0
X-Received: by 10.68.17.230 with SMTP id r6mr21790845pbd.112.1379537468893;\n Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
Received: by 10.70.43.236 with HTTP; Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
Date: Wed, 18 Sep 2013 21:51:08 +0100
Message-Id: <CAE=3ySAxx8r7dcgaixQjbYS3J4mVG7FdEqAwDAcJ4bkHnDiT5w@mail.gmail.com>
Subject: test1
From: A User <5555555555@tmomail.net>
To: test@test.mailgun.org
Content-Type: multipart/alternative; boundary=\"bcaec520ee93c9b26104e6ae9838\"
X-Mailgun-Incoming: Yes
--bcaec520ee93c9b26104e6ae9838
Content-Type: text/plain; charset=ISO-8859-1
--bcaec520ee93c9b26104e6ae9838
Content-Type: text/html; charset=ISO-8859-1
<div dir=\"ltr\"><br></div>
--bcaec520ee93c9b26104e6ae9838--

27
test/fixtures/mailgun_message2.txt vendored Normal file
View file

@ -0,0 +1,27 @@
Received: by luna.mailgun.net with SMTP mgrt 8745468409521; Wed, 18 Sep 2013\n 20:51:11 +0000
X-Envelope-From: <incoming@othermail.com>
Received: from mail-pa0-f45.google.com (mail-pa0-f45.google.com\n [209.85.220.45]) by mxa.mailgun.org with ESMTP id\n 523a123d.7f3a16482330-in1; Wed, 18 Sep 2013 20:51:09 -0000 (UTC)
Received: by mail-pa0-f45.google.com with SMTP id bg4so8745646pad.18 for\n <5555555555@tmomail.net>; Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com;\n s=20120113; h=mime-version:date:message-id:subject:from:to:content-type;\n bh=XwBMHRr9XT/f5w7Vzap//yQJ9VoVaVcjEQ5+xjgSoMk=;\n b=IsSDTe2jY8XucXOxviOrGg1upSm5IOddtOsHFpgcyND4F9glSIU+xixRK60/9Dbqae\n BT5On3C+Aoffpj/WUqnp1+hA89JYDbWhzXcJPhkW8FcFnPvAzQ0G/loV7+CafWLubEnp\n T61owZQAQgdkHsrZMf1ChmpghNMJuDLMJvYmiijIId+z7arBocjErhHqwRY4Y6lmFdTo\n s9uGtVJ7Av1g7m8KyGZY6kzh/XPhbr4B0tjIFpyp+bDFH2Nk290RAB/2garNBfQAPhzy\n hIvvbuz5MLFWSnW17eXdymHAEH6oSbRfar8ocxcY5T+hg++nfsegUJ6sPRG1G63qnsj4 dCig==
Mime-Version: 1.0
X-Received: by 10.68.17.230 with SMTP id r6mr21790845pbd.112.1379537468893;\n Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
Received: by 10.70.43.236 with HTTP; Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
Date: Wed, 18 Sep 2013 21:51:08 +0100
Message-Id: <CAE=3ySAxx8r7dcgaixQjbYS3J4mVG7FdEqAwDAcJ4bkHnDiT5w@mail.gmail.com>
Subject: test
From: A User <incoming@othermail.com>
To: 5555555555@tmomail.net
Content-Type: multipart/alternative; boundary=\"bcaec520ee93c9b26104e6ae9838\"
X-Mailgun-Incoming: Yes
--bcaec520ee93c9b26104e6ae9838
Content-Type: text/plain; charset=ISO-8859-1
--bcaec520ee93c9b26104e6ae9838
Content-Type: text/html; charset=ISO-8859-1
<div dir=\"ltr\"><br></div>
--bcaec520ee93c9b26104e6ae9838--

View file

@ -175,9 +175,6 @@ class RecurringTodoTest < ActiveSupport::TestCase
assert_equal @sunday, due_date # june 8th
due_date = @monthly.get_due_date(@sunday) # june 8th
assert_equal Time.zone.local(2008,6,8), due_date # june 8th
due_date = @monthly.get_due_date(@monday) # june 9th
assert_equal Time.zone.local(2008,8,8), due_date # aug 8th
end
@ -247,7 +244,7 @@ class RecurringTodoTest < ActiveSupport::TestCase
# every_day should return start_day if it is in the future
@every_day.start_from = @in_three_days
due_date = @every_day.get_due_date(nil)
assert_equal @in_three_days, due_date
assert_equal @in_three_days.to_s(:db), due_date.to_s(:db)
due_date = @every_day.get_due_date(@tomorrow)
assert_equal @in_three_days, due_date
@ -354,6 +351,66 @@ class RecurringTodoTest < ActiveSupport::TestCase
assert !@every_month.valid?
end
def test_set_every_n_days_from_form_input
todo = RecurringTodo.new({
:description => "Task every 2 days",
:context => Context.first,
:recurring_target => "show_from_date",
:start_from => "01/01/01",
:ends_on => "no_end_date",
:recurring_period => "daily",
:daily_selector => "daily_every_x_day",
:daily_every_x_days => 2,
})
assert todo.valid?, todo.errors.full_messages
assert_equal 2, todo.every_other1
end
def test_set_every_n_weeks_from_form_input
todo = RecurringTodo.new({
:description => "Task every 3 weeks",
:context => Context.first,
:recurring_target => "show_from_date",
:start_from => "01/01/01",
:ends_on => "no_end_date",
:recurring_period => "weekly",
:weekly_every_x_week => 3,
:weekly_return_monday => "m",
})
assert todo.valid?, todo.errors.full_messages
assert_equal 3, todo.every_other1
assert todo.on_monday
end
def test_set_every_n_months_from_form_input
todo = RecurringTodo.new({
:description => "Task every 4 months",
:context => Context.first,
:recurring_target => "show_from_date",
:start_from => "01/01/01",
:ends_on => "no_end_date",
:recurring_period => "monthly",
:monthly_selector => "monthly_every_x_day",
:monthly_every_x_day => 1,
:monthly_every_x_month => 4,
})
assert todo.valid?, todo.errors.full_messages
assert_equal 4, todo.every_other2
end
def test_set_yearly_from_form_input
todo = RecurringTodo.new({
:description => "Task every year in May",
:context => Context.first,
:recurring_target => "show_from_date",
:start_from => "01/01/01",
:ends_on => "no_end_date",
:recurring_period => "yearly",
:yearly_selector => "yearly_every_x_day",
:yearly_every_x_day => 15,
:yearly_month_of_year => 5,
})
assert todo.valid?, todo.errors.full_messages
assert_equal 5, todo.every_other2
end
end

View file

@ -0,0 +1,36 @@
require 'net/https'
require './test/minimal_test_helper'
require './doc/tracks_cli/tracks_api'
module TracksCli
class TracksApiTest < Test::Unit::TestCase
def test_https_detection
uri = URI.parse("https://tracks.example.com")
http = TracksCli::TracksAPI.new({}).get_http(uri)
assert http.use_ssl?, "ssl expected"
uri = URI.parse("http://tracks.example.com")
http = TracksCli::TracksAPI.new({}).get_http(uri)
assert !http.use_ssl?, "no ssl expected"
end
def test_context_uri
uri = TracksCli::TracksAPI.new({context_prefix: "c"}).context_uri_for(16)
assert_equal "c16.xml", uri.path
uri = TracksCli::TracksAPI.new({context_prefix: "c"}).context_uri_for(18)
assert_equal "c18.xml", uri.path
end
def test_static_uris_for_todo_and_project
uri = TracksCli::TracksAPI.new({projects_uri: "https//tracks.example.com/projects.xml"}).project_uri
assert_equal "https//tracks.example.com/projects.xml", uri.path
uri = TracksCli::TracksAPI.new({uri: "https//tracks.example.com/todos.xml"}).todo_uri
assert_equal "https//tracks.example.com/todos.xml", uri.path
end
end
end

View file

@ -0,0 +1,81 @@
require './test/minimal_test_helper'
require './doc/tracks_cli/tracks_xml_builder'
require 'active_support/time_with_zone'
module TracksCli
class TracksXmlBuilderTest < Test::Unit::TestCase
def test_all
todo = {
description: "test action",
project_id: 1,
show_from: Time.utc(2013,1,1,14,00,00),
notes: "action notes",
taglist: "one, two",
context_name: "@home",
is_dependend: true,
predecessor: 123
}
xml = TracksCli::TracksXmlBuilder.new.build_todo_xml(todo)
expect = "<todo><description>test action</description>" +
"<project_id>1</project_id><show-from type=\"datetime\">#{Time.at(todo[:show_from]).xmlschema}</show-from>" +
"<notes>action notes</notes><tags><tag><name>one</name></tag><tag><name>two</name></tag></tags>" +
"<context><name>@home</name></context><predecessor_dependencies><predecessor>123</predecessor></predecessor_dependencies></todo>"
assert_equal expect, xml
end
def test_context_name_is_passed_through
todo = {
description: "test action",
project_id: 1,
context_name: "@home",
}
xml = TracksCli::TracksXmlBuilder.new.build_todo_xml(todo)
expect = "<todo><description>test action</description><project_id>1</project_id><context><name>@home</name></context></todo>"
assert_equal expect, xml
end
def test_context_id_is_used_if_no_context_name_given
todo = {
description: "test action",
project_id: 5,
context_id: 16,
}
xml = TracksCli::TracksXmlBuilder.new.build_todo_xml(todo)
expect = "<todo><description>test action</description><project_id>5</project_id><context_id>16</context_id></todo>"
assert_equal expect, xml, "only context_id given, so that should be included"
todo = {
description: "test action",
project_id: 5,
context_id: 16,
context_name: "@inbox"
}
xml = TracksCli::TracksXmlBuilder.new.build_todo_xml(todo)
expect = "<todo><description>test action</description><project_id>5</project_id><context><name>@inbox</name></context></todo>"
assert_equal expect, xml, "both context_id and context_name given, then context_name should be used"
end
def test_project_xml_all
todo = {
description: "test project",
default_context_id: 16
}
xml = TracksCli::TracksXmlBuilder.new.build_project_xml(todo)
expect = "<project><name>test project</name><default-context-id>16</default-context-id></project>"
assert_equal expect, xml
end
end
end

View file

@ -1,4 +1,9 @@
ENV["RAILS_ENV"] ||= "test"
# test coverage from codeclimate
require "codeclimate-test-reporter"
CodeClimate::TestReporter.start
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'