Merge pull request #225 from HPI-BP2015H/cronjobs

Cronjobs
This commit is contained in:
Konstantin Haase 2016-04-22 15:05:52 +02:00
commit 0458482fdf
32 changed files with 1110 additions and 14 deletions

View File

@ -46,6 +46,7 @@ group :test do
gem 'factory_girl', '~> 2.4.0'
gem 'mocha', '~> 0.12'
gem 'database_cleaner', '~> 0.8.0'
gem 'timecop', '~> 0.8.0'
end
group :development do

View File

@ -73,7 +73,7 @@ GIT
GIT
remote: git://github.com/travis-ci/travis-migrations.git
revision: fcf6eea3e3122a7cbb857826db835de69974c54d
revision: 3f6bb84800b0222ceba95a4b1368969eb5ede8e0
specs:
travis-migrations (0.0.1)
@ -358,6 +358,7 @@ GEM
thor (0.19.1)
thread_safe (0.3.5)
tilt (1.4.1)
timecop (0.8.0)
timers (4.0.1)
hitimes
tool (0.2.3)
@ -414,6 +415,7 @@ DEPENDENCIES
sinatra-contrib
skylight (~> 0.6.0.beta.1)
stackprof
timecop (~> 0.8.0)
travis-amqp!
travis-api!
travis-config (~> 0.1.0)

View File

@ -59,6 +59,14 @@ module Travis::API::V3
visible? branch.repository
end
def cron_visible?(cron)
Travis::Features.owner_active?(:cron, cron.branch.repository.owner) and visible? cron.branch.repository
end
def cron_writable?(cron)
Travis::Features.owner_active?(:cron, cron.branch.repository.owner) and writable? cron.branch.repository
end
def job_visible?(job)
visible? job.repository
end

View File

@ -4,6 +4,7 @@ module Travis::API::V3
belongs_to :last_build, class_name: 'Travis::API::V3::Models::Build'.freeze
has_many :builds, foreign_key: [:repository_id, :branch], primary_key: [:repository_id, :name], order: 'builds.id DESC'.freeze, conditions: { event_type: 'push' }
has_many :commits, foreign_key: [:repository_id, :branch], primary_key: [:repository_id, :name], order: 'commits.id DESC'.freeze
has_one :cron, dependent: :delete
def default_branch
name == repository.default_branch_name

View File

@ -0,0 +1,75 @@
module Travis::API::V3
class Models::Cron < Model
belongs_to :branch
LastBuild = -1
ThisBuild = 0
NextBuild = 1
def next_enqueuing
if disable_by_build && last_non_cron_build_date > planned_time(LastBuild)
planned_time(NextBuild)
elsif last_cron_build_date >= planned_time(LastBuild)
planned_time(ThisBuild)
else
Time.now
end
end
def planned_time(in_builds = ThisBuild)
case interval
when 'daily'
planned_time_daily(in_builds)
when 'weekly'
planned_time_weekly(in_builds)
when 'monthly'
planned_time_monthly(in_builds)
end
end
def planned_time_daily(in_builds)
now = DateTime.now
build_today = DateTime.new(now.year, now.month, now.day, created_at.hour)
return build_today + 1 + in_builds if (now > build_today)
build_today + in_builds
end
def planned_time_weekly(in_builds)
now = DateTime.now
build_today = DateTime.new(now.year, now.month, now.day, created_at.hour)
next_time = build_today + ((created_at.wday - now.wday) % 7)
return build_today + 7 * (1 + in_builds) if (now > next_time)
next_time + 7 * in_builds
end
def planned_time_monthly(in_builds)
now = DateTime.now
created = DateTime.new(created_at.year, created_at.month, created_at.day, created_at.hour)
month_since_creation = (now.year * 12 + now.month) - (created_at.year * 12 + created_at.month)
this_month = created >> month_since_creation
return created >> (month_since_creation + 1 + in_builds) if (now > this_month)
created >> (month_since_creation + in_builds)
end
def last_cron_build_date
last_cron_build = Models::Build.where(
:repository_id => branch.repository.id,
:branch => branch.name,
:event_type => 'cron'
).order("id DESC").first
return last_cron_build.created_at unless last_cron_build.nil?
Time.at(0)
end
def last_non_cron_build_date
last_build = Models::Build.where(
:repository_id => branch.repository.id,
:branch => branch.name
).where(['event_type NOT IN (?)', ['cron']]).order("id DESC").first
return last_build.created_at unless last_build.nil?
Time.at(0)
end
end
end

View File

@ -0,0 +1,13 @@
require 'travis/api/v3/permissions/generic'
module Travis::API::V3
class Permissions::Cron < Permissions::Generic
def delete?
write? and Travis::Features.owner_active?(:cron, object.branch.repository.owner)
end
def start?
Travis::Features.owner_active?(:cron, object.branch.repository.owner)
end
end
end

View File

@ -21,5 +21,9 @@ module Travis::API::V3
def create_request?
write?
end
def create_cron?
Travis::Features.owner_active?(:cron, object.owner) and write?
end
end
end

View File

@ -0,0 +1,21 @@
module Travis::API::V3
class Queries::Cron < Query
params :id
sortable_by :id
def find
return Models::Cron.find_by_id(id) if id
raise WrongParams, 'missing cron.id'.freeze
end
def find_for_branch(branch)
branch.cron
end
def create(branch, interval, disable_by_build)
branch.cron.destroy unless branch.cron.nil?
Models::Cron.create(branch: branch, interval: interval, disable_by_build: disable_by_build)
end
end
end

View File

@ -0,0 +1,35 @@
module Travis::API::V3
class Queries::Crons < Query
def find(repository)
Models::Cron.where(:branch_id => repository.branches)
end
def start_all()
Models::Cron.all.select do |cron|
start(cron) if cron.next_enqueuing <= Time.now
end
end
def start(cron)
branch = cron.branch
raise ServerError, 'repository does not have a github_id'.freeze unless branch.repository.github_id
unless branch.exists_on_github
cron.destroy
return false
end
user_id = branch.repository.users.detect { |u| u.github_oauth_token }.id
payload = {
repository: { id: branch.repository.github_id, owner_name: branch.repository.owner_name, name: branch.repository.name },
branch: branch.name,
user: { id: user_id }
}
class_name, queue = Query.sidekiq_queue(:build_request)
::Sidekiq::Client.push('queue'.freeze => queue, 'class'.freeze => class_name, 'args'.freeze => [{type: 'cron'.freeze, payload: JSON.dump(payload), credentials: {}}])
true
end
end
end

View File

@ -45,7 +45,7 @@ module Travis::API::V3
when Hash then value.map { |k, v| [k, render_value(v, **options)] }.to_h
when Array then value.map { |v | render_value(v, **options) }
when *PRIMITIVE then value
when Time then value.strftime('%Y-%m-%dT%H:%M:%SZ')
when Time, DateTime then value.strftime('%Y-%m-%dT%H:%M:%SZ')
when Model then render_model(value, **options)
when ActiveRecord::Relation then render_value(value.to_a, **options)
when ActiveRecord::Associations::CollectionProxy then render_value(value.to_a, **options)

View File

@ -0,0 +1,13 @@
require 'travis/api/v3/renderer/model_renderer'
module Travis::API::V3
class Renderer::Cron < Renderer::ModelRenderer
representation(:minimal, :id)
representation(:standard, :id, :repository, :branch, :interval, :disable_by_build, :next_enqueuing)
def repository
model.branch.repository
end
end
end

View File

@ -0,0 +1,6 @@
module Travis::API::V3
class Renderer::Crons < Renderer::CollectionRenderer
type :crons
collection_key :crons
end
end

View File

@ -27,6 +27,14 @@ module Travis::API::V3
end
end
resource :cron do
capture id: :digit
route '/cron/{cron.id}'
get :find
delete :delete
end
resource :job do
capture id: :digit
route '/job/{job.id}'
@ -81,6 +89,12 @@ module Travis::API::V3
resource :branch do
route '/branch/{branch.name}'
get :find
resource :cron do
route '/cron'
get :for_branch
post :create
end
end
resource :branches do
@ -93,6 +107,11 @@ module Travis::API::V3
get :find
end
resource :crons do
route '/crons'
get :for_repository
end
resource :requests do
route '/requests'
get :find

View File

@ -60,6 +60,10 @@ module Travis::API::V3
current_resource.add_service('POST'.freeze, *args)
end
def delete(*args)
current_resource.add_service('DELETE'.freeze, *args)
end
def draw_routes
resources.each do |resource|
prefix = resource.route

View File

@ -9,6 +9,8 @@ module Travis::API::V3
Broadcasts = Module.new { extend Services }
Build = Module.new { extend Services }
Builds = Module.new { extend Services }
Cron = Module.new { extend Services }
Crons = Module.new { extend Services }
Job = Module.new { extend Services }
Jobs = Module.new { extend Services }
Lint = Module.new { extend Services }

View File

@ -0,0 +1,18 @@
module Travis::API::V3
class Services::Cron::Create < Service
result_type :cron
params :interval, :disable_by_build
def run!
raise LoginRequired unless access_control.logged_in? or access_control.full_access?
raise NotFound unless repository = find(:repository)
raise NotFound unless branch = find(:branch, repository)
raise Error.new('Crons can only be set up for branches existing on GitHub!', status: 422) unless branch.exists_on_github
raise Error.new('Invalid value for interval. Interval must be "daily", "weekly" or "monthly"!', status: 422) unless ["daily", "weekly", "monthly"].include?(params["interval"])
access_control.permissions(repository).create_cron!
access_control.permissions(branch.cron).delete! if branch.cron
query.create(branch, params["interval"], params["disable_by_build"] ? params["disable_by_build"] : false)
end
end
end

View File

@ -0,0 +1,12 @@
module Travis::API::V3
class Services::Cron::Delete < Service
#params :id
def run!
raise LoginRequired unless access_control.logged_in? or access_control.full_access?
cron = find
access_control.permissions(cron).delete!
cron.destroy
end
end
end

View File

@ -0,0 +1,9 @@
module Travis::API::V3
class Services::Cron::Find < Service
#params :id
def run!
find
end
end
end

View File

@ -0,0 +1,10 @@
module Travis::API::V3
class Services::Cron::ForBranch < Service
def run!
repo = find(:repository)
raise InsufficientAccess unless Travis::Features.owner_active?(:cron, repo.owner)
query.find_for_branch(find(:branch, repo))
end
end
end

View File

@ -0,0 +1,11 @@
module Travis::API::V3
class Services::Crons::ForRepository < Service
paginate
def run!
repo = find(:repository)
raise InsufficientAccess unless Travis::Features.owner_active?(:cron, repo.owner)
query.find(repo)
end
end
end

View File

@ -0,0 +1,9 @@
module Travis::API::V3
class Services::Crons::Start < Service
def run!
query.start_all()
end
end
end

231
spec/v3/models/cron_spec.rb Normal file
View File

@ -0,0 +1,231 @@
require 'spec_helper'
require 'timecop'
describe Travis::API::V3::Models::Cron do
let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first }
let(:branch) { Travis::API::V3::Models::Branch.create(repository: repo, name: 'cron test') }
describe "next build time is calculated correctly on year changes" do
before do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
end
after do
Timecop.return
end
it "for daily builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'daily', disable_by_build: false)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 1, 16)
build.destroy
cron.destroy
end
it "for weekly builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'weekly', disable_by_build: false)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 7, 16)
build.destroy
cron.destroy
end
it "for monthly builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'monthly', disable_by_build: false)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 31, 16)
build.destroy
cron.destroy
end
end
describe "push build is ignored if disable by build is false" do
before do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
end
after do
Timecop.return
end
it "for daily builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'daily', disable_by_build: false)
cron_build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
push_build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'push')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 1, 16)
cron_build.destroy
push_build.destroy
cron.destroy
end
it "for weekly builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'weekly', disable_by_build: false)
cron_build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
push_build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'push')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 7, 16)
cron_build.destroy
push_build.destroy
cron.destroy
end
it "for monthly builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'monthly', disable_by_build: false)
cron_build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
push_build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'push')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 31, 16)
cron_build.destroy
push_build.destroy
cron.destroy
end
end
describe "disable by build works with build" do
before do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
end
after do
Timecop.return
end
it "for daily builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'daily', disable_by_build: true)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'push')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 2, 16)
build.destroy
cron.destroy
end
it "for weekly builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'weekly', disable_by_build: true)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'push')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 14, 16)
build.destroy
cron.destroy
end
it "for monthly builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'monthly', disable_by_build: true)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'push')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 2, 29, 16) # it's a leap year :-D
build.destroy
cron.destroy
end
end
describe "disable by build works without build" do
before do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
end
after do
Timecop.return
end
it "for daily builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'daily', disable_by_build: true)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 1, 16)
build.destroy
cron.destroy
end
it "for weekly builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'weekly', disable_by_build: true)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 7, 16)
build.destroy
cron.destroy
end
it "for monthly builds" do
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'monthly', disable_by_build: true)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
expect(cron.next_enqueuing).to be == DateTime.new(2016, 1, 31, 16)
build.destroy
cron.destroy
end
end
describe "build starts now if next build time is in the past" do
before do
# nothing, this time
# time freeze is performed in examples
end
after do
Timecop.return
end
it "for daily builds with disable_by_build true" do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'daily', disable_by_build: true)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
Timecop.freeze(DateTime.new(2016, 1, 1, 19))
expect(cron.next_enqueuing).to be == DateTime.now
build.destroy
cron.destroy
end
it "for daily builds with disable_by_build false" do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'daily', disable_by_build: false)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
Timecop.freeze(DateTime.new(2016, 1, 1, 19))
expect(cron.next_enqueuing).to be == DateTime.now
build.destroy
cron.destroy
end
it "for weekly builds with disable_by_build true" do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'weekly', disable_by_build: true)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
Timecop.freeze(DateTime.new(2016, 1, 7, 19))
expect(cron.next_enqueuing).to be == DateTime.now
build.destroy
cron.destroy
end
it "for weekly builds with disable_by_build false" do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'weekly', disable_by_build: false)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
Timecop.freeze(DateTime.new(2016, 1, 7, 19))
expect(cron.next_enqueuing).to be == DateTime.now
build.destroy
cron.destroy
end
it "for monthly builds with disable_by_build true" do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'monthly', disable_by_build: true)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
Timecop.freeze(DateTime.new(2016, 1, 31, 19))
expect(cron.next_enqueuing).to be == DateTime.now
build.destroy
cron.destroy
end
it "for monthly builds with disable_by_build false" do
Timecop.travel(DateTime.new(2015, 12, 31, 16))
cron = Travis::API::V3::Models::Cron.create(branch_id: branch.id, interval: 'monthly', disable_by_build: false)
build = Travis::API::V3::Models::Build.create(:repository_id => repo.id, :branch_name => branch.name, :event_type => 'cron')
Timecop.freeze(DateTime.new(2016, 1, 31, 19))
expect(cron.next_enqueuing).to be == DateTime.now
build.destroy
cron.destroy
end
end
end

View File

@ -0,0 +1,27 @@
require 'spec_helper'
describe Travis::API::V3::Queries::Crons do
let(:user) { Travis::API::V3::Models::User.find_by_login('svenfuchs') }
let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first }
let(:existing_branch) { Travis::API::V3::Models::Branch.create(repository: repo, name: 'cron-test-existing', exists_on_github: true) }
let(:non_existing_branch) { Travis::API::V3::Models::Branch.create(repository: repo, name: 'cron-test-non-existing', exists_on_github: false) }
let(:query) { Travis::API::V3::Queries::Crons.new({}, 'Overview')
}
describe "start all" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) }
it "starts crons on existing branches" do
cron = Travis::API::V3::Models::Cron.create(branch_id: existing_branch.id, interval: 'daily', disable_by_build: false)
expect(query.start_all).to include(cron)
end
it "delete crons on branches not existing on GitHub" do
cron = Travis::API::V3::Models::Cron.create(branch_id: non_existing_branch.id, interval: 'daily', disable_by_build: false)
expect(query.start_all).to_not include(cron)
expect(Travis::API::V3::Models::Cron.where(id: cron.id).length).to equal(0)
end
end
end

View File

@ -0,0 +1,153 @@
require 'spec_helper'
describe Travis::API::V3::Services::Cron::Create do
let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first }
let(:branch) { Travis::API::V3::Models::Branch.where(repository_id: repo).first }
let(:non_existing_branch) { Travis::API::V3::Models::Branch.create(repository: repo, name: 'cron-test', exists_on_github: false) }
let(:last_cron) {Travis::API::V3::Models::Cron.where(branch_id: branch.id).last}
let(:current_cron) {Travis::API::V3::Models::Cron.where(branch_id: branch.id).last}
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}", "Content-Type" => "application/json" }}
let(:options) {{ "interval" => "monthly", "disable_by_build" => false }}
let(:wrong_options) {{ "interval" => "notExisting", "disable_by_build" => false }}
let(:parsed_body) { JSON.load(body) }
before do
Travis::Features.activate_owner(:cron, repo.owner)
end
describe "creating a cron job with feature flag disabled" do
before { Travis::Features.deactivate_owner(:cron, repo.owner) }
before { post("/v3/repo/#{repo.id}/branch/#{branch.name}/cron", options, headers)}
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "insufficient_access",
"error_message" => "operation requires create_cron access to repository",
"resource_type" => "repository",
"permission" => "create_cron",
"repository" => {
"@type" => "repository",
"@href" => "/repo/#{repo.id}", # should be /v3/repo/#{repo.id}
"@representation" => "minimal",
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal" }
}}
end
describe "creating a cron job" do
before { last_cron }
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) }
before { post("/v3/repo/#{repo.id}/branch/#{branch.name}/cron", options, headers) }
example { expect(current_cron == last_cron).to be_falsey }
example { expect(last_response).to be_ok }
example { expect(parsed_body).to be == {
"@type" => "cron",
"@href" => "/v3/cron/#{current_cron.id}",
"@representation" => "standard",
"@permissions" => {
"read" => true,
"delete" => true,
"start" => true },
"id" => current_cron.id,
"repository" => {
"@type" => "repository",
"@href" => "/v3/repo/#{repo.id}",
"@representation" => "minimal",
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal" },
"branch" => {
"@type" => "branch",
"@href" => "/v3/repo/#{repo.id}/branch/#{branch.name}",
"@representation" => "minimal",
"name" => "#{branch.name}" },
"interval" => "monthly",
"disable_by_build" => false,
"next_enqueuing" => current_cron.next_enqueuing.strftime('%Y-%m-%dT%H:%M:%SZ')
}}
end
describe "creating multiple cron jobs for one branch" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) }
before { post("/v3/repo/#{repo.id}/branch/#{branch.name}/cron", options, headers) }
before { post("/v3/repo/#{repo.id}/branch/#{branch.name}/cron", options, headers) }
it "only stores one" do
expect(Travis::API::V3::Models::Cron.where(branch_id: branch.id).count).to eq(1)
end
end
describe "creating a cron job with a wrong interval" do
before { last_cron }
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) }
before { post("/v3/repo/#{repo.id}/branch/#{branch.name}/cron", wrong_options, headers) }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "error",
"error_message" => "Invalid value for interval. Interval must be \"daily\", \"weekly\" or \"monthly\"!"
}}
end
describe "creating a cron job on a branch not existing on GitHub" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) }
before { post("/v3/repo/#{repo.id}/branch/#{non_existing_branch.name}/cron", options, headers) }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "error",
"error_message" => "Crons can only be set up for branches existing on GitHub!"
}}
end
describe "try creating a cron job without login" do
before { post("/v3/repo/#{repo.id}/branch/#{branch.name}/cron", options) }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "login_required",
"error_message" => "login required"
}}
end
describe "try creating a cron job with a user without permissions" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: false) }
before { post("/v3/repo/#{repo.id}/branch/#{branch.name}/cron", options, headers) }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "insufficient_access",
"error_message" => "operation requires create_cron access to repository",
"resource_type" => "repository",
"permission" => "create_cron",
"repository" => {
"@type" => "repository",
"@href" => "/repo/#{repo.id}", # should be /v3/repo/#{repo.id}
"@representation" => "minimal",
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal" }
}}
end
describe "creating cron on a non-existing repository by slug" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: false) }
before { post("/v3/repo/svenfuchs%2Fminimal1/branch/master/cron", options, headers) }
example { expect(last_response).to be_not_found }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "repository not found (or insufficient access)",
"resource_type" => "repository"
}}
end
describe "creating cron on a non-existing branch" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: false) }
before { post("/v3/repo/#{repo.id}/branch/hopefullyNonExistingBranch/cron", options, headers) }
example { expect(last_response).to be_not_found }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "branch not found (or insufficient access)",
"resource_type" => "branch"
}}
end
end

View File

@ -0,0 +1,98 @@
require 'spec_helper'
describe Travis::API::V3::Services::Cron::Delete do
let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first }
let(:branch) { Travis::API::V3::Models::Branch.where(repository_id: repo).first }
let(:cron) { Travis::API::V3::Models::Cron.create(branch: branch, interval:'daily') }
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
let(:parsed_body) { JSON.load(body) }
before do
Travis::Features.activate_owner(:cron, repo.owner)
end
describe "deleting cron jobs with feature disabled" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) }
before { Travis::Features.deactivate_owner(:cron, repo.owner) }
before { delete("/v3/cron/#{cron.id}", {}, headers)}
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "cron not found (or insufficient access)",
"resource_type" => "cron"
}}
end
describe "deleting a cron job by id" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) }
before { delete("/v3/cron/#{cron.id}", {}, headers) }
example { expect(last_response).to be_ok }
example { expect(Travis::API::V3::Models::Cron.where(id: cron.id)).to be_empty }
example { expect(parsed_body).to be == {
"@type" => "cron",
"@href" => "/v3/cron/#{cron.id}",
"@representation" => "standard",
"@permissions" => {
"read" => true,
"delete" => true,
"start" => true },
"id" => cron.id,
"repository" => {
"@type" => "repository",
"@href" => "/v3/repo/#{repo.id}",
"@representation" => "minimal",
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal" },
"branch" => {
"@type" => "branch",
"@href" => "/v3/repo/#{repo.id}/branch/#{branch.name}",
"@representation" => "minimal",
"name" => branch.name },
"interval" => "daily",
"disable_by_build" => true,
"next_enqueuing" => cron.next_enqueuing.strftime('%Y-%m-%dT%H:%M:%SZ')
}}
end
describe "try deleting a cron job without login" do
before { delete("/v3/cron/#{cron.id}") }
example { expect(Travis::API::V3::Models::Cron.where(id: cron.id)).to exist }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "login_required",
"error_message" => "login required"
}}
end
describe "try deleting a cron job with a user without permissions" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: false) }
before { delete("/v3/cron/#{cron.id}", {}, headers) }
example { expect(Travis::API::V3::Models::Cron.where(id: cron.id)).to exist }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "insufficient_access",
"error_message" => "operation requires delete access to cron",
"resource_type" => "cron",
"permission" => "delete",
"cron" => {
"@type" => "cron",
"@href" => "/cron/#{cron.id}", # should be /v3/cron/#{cron.id}
"@representation" => "minimal",
"id" => cron.id }
}}
end
describe "try deleting a non-existing cron job" do
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: false) }
before { delete("/v3/cron/999999999999999", {}, headers) }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "cron not found (or insufficient access)",
"resource_type" => "cron"
}}
end
end

View File

@ -0,0 +1,113 @@
require 'spec_helper'
describe Travis::API::V3::Services::Cron::Find do
let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first }
let(:branch) { Travis::API::V3::Models::Branch.where(repository_id: repo).first }
let(:cron) { Travis::API::V3::Models::Cron.create(branch: branch, interval:'daily') }
let(:parsed_body) { JSON.load(body) }
before do
Travis::Features.activate_owner(:cron, repo.owner)
end
describe "find cron job with feature disabled" do
before { Travis::Features.deactivate_owner(:cron, repo.owner) }
before { get("/v3/cron/#{cron.id}") }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "cron not found (or insufficient access)",
"resource_type" => "cron"
}}
end
describe "fetching a cron job by id" do
before { get("/v3/cron/#{cron.id}") }
example { expect(last_response).to be_ok }
example { expect(parsed_body).to be == {
"@type" => "cron",
"@href" => "/v3/cron/#{cron.id}",
"@representation" => "standard",
"@permissions" => {
"read" => true,
"delete" => false,
"start" => true },
"id" => cron.id,
"repository" => {
"@type" => "repository",
"@href" => "/v3/repo/#{repo.id}",
"@representation" => "minimal",
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal" },
"branch" => {
"@type" => "branch",
"@href" => "/v3/repo/#{repo.id}/branch/#{branch.name}",
"@representation" => "minimal",
"name" => branch.name },
"interval" => "daily",
"disable_by_build" => true,
"next_enqueuing" => cron.next_enqueuing.strftime('%Y-%m-%dT%H:%M:%SZ')
}}
end
describe "fetching a non-existing cron job by id" do
before { get("/v3/cron/999999999999999") }
example { expect(last_response).to be_not_found }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "cron not found (or insufficient access)",
"resource_type" => "cron"
}}
end
describe "private cron, not authenticated" do
before { repo.update_attribute(:private, true) }
before { get("/v3/cron/#{cron.id}") }
after { repo.update_attribute(:private, false) }
example { expect(last_response).to be_not_found }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "cron not found (or insufficient access)",
"resource_type" => "cron"
}}
end
describe "private cron, authenticated as user with access" do
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }}
before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, pull: true) }
before { repo.update_attribute(:private, true) }
before { get("/v3/cron/#{cron.id}", {}, headers) }
after { repo.update_attribute(:private, false) }
example { expect(last_response).to be_ok }
example { expect(parsed_body).to be == {
"@type" => "cron",
"@href" => "/v3/cron/#{cron.id}",
"@representation" => "standard",
"@permissions" => {
"read" => true,
"delete" => false,
"start" => true },
"id" => cron.id,
"repository" => {
"@type" => "repository",
"@href" => "/v3/repo/#{repo.id}",
"@representation" => "minimal",
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal" },
"branch" => {
"@type" => "branch",
"@href" => "/v3/repo/#{repo.id}/branch/#{branch.name}",
"@representation" => "minimal",
"name" => branch.name },
"interval" => "daily",
"disable_by_build" => true,
"next_enqueuing" => cron.next_enqueuing.strftime('%Y-%m-%dT%H:%M:%SZ')
}}
end
end

View File

@ -0,0 +1,90 @@
require 'spec_helper'
describe Travis::API::V3::Services::Cron::ForBranch do
let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first }
let(:branch) { Travis::API::V3::Models::Branch.where(repository_id: repo).first }
let(:cron) { Travis::API::V3::Models::Cron.create(branch: branch, interval:'daily') }
let(:parsed_body) { JSON.load(body) }
before do
Travis::Features.activate_owner(:cron, repo.owner)
end
describe "find cron job for branch with feature disabled" do
before { cron }
before { Travis::Features.deactivate_owner(:cron, repo.owner) }
before { get("/v3/repo/#{repo.id}/branch/#{branch.name}/cron") }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "insufficient_access",
"error_message" => "forbidden"
}}
end
describe "fetching all crons by repo id" do
before { cron }
before { get("/v3/repo/#{repo.id}/branch/#{branch.name}/cron") }
example { expect(last_response).to be_ok }
example { expect(parsed_body).to be == {
"@type" => "cron",
"@href" => "/v3/cron/#{cron.id}",
"@representation" => "standard",
"@permissions" => {
"read" => true,
"delete" => false,
"start" => true },
"id" => cron.id,
"repository" => {
"@type" => "repository",
"@href" => "/v3/repo/#{repo.id}",
"@representation" => "minimal",
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal" },
"branch" => {
"@type" => "branch",
"@href" => "/v3/repo/#{repo.id}/branch/#{branch.name}",
"@representation" => "minimal",
"name" => branch.name },
"interval" => "daily",
"disable_by_build" => true,
"next_enqueuing" => cron.next_enqueuing.strftime('%Y-%m-%dT%H:%M:%SZ')
}}
end
describe "fetching crons on a non-existing repository by slug" do
before { get("/v3/repo/svenfuchs%2Fminimal1/branch/master/cron") }
example { expect(last_response).to be_not_found }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "repository not found (or insufficient access)",
"resource_type" => "repository"
}}
end
describe "fetching crons on a non-existing branch" do
before { get("/v3/repo/#{repo.id}/branch/hopefullyNonExistingBranch/cron") }
example { expect(last_response).to be_not_found }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "branch not found (or insufficient access)",
"resource_type" => "branch"
}}
end
describe "fetching crons from private repo, not authenticated" do
before { repo.update_attribute(:private, true) }
before { get("/v3/repo/#{repo.id}/branch/#{branch.name}/cron") }
after { repo.update_attribute(:private, false) }
example { expect(last_response).to be_not_found }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "repository not found (or insufficient access)",
"resource_type" => "repository"
}}
end
end

View File

@ -0,0 +1,101 @@
require 'spec_helper'
describe Travis::API::V3::Services::Crons::ForRepository do
let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first }
let(:branch) { Travis::API::V3::Models::Branch.where(repository_id: repo).first }
let(:cron) { Travis::API::V3::Models::Cron.create(branch: branch, interval:'daily') }
let(:parsed_body) { JSON.load(body) }
before do
Travis::Features.activate_owner(:cron, repo.owner)
end
describe "fetching all crons by repo id with feature disabled" do
before { Travis::Features.deactivate_owner(:cron, repo.owner) }
before { get("/v3/repo/#{repo.id}/crons") }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "insufficient_access",
"error_message" => "forbidden"
}}
end
describe "fetching all crons by repo id" do
before { cron }
before { get("/v3/repo/#{repo.id}/crons") }
example { expect(last_response).to be_ok }
example { expect(parsed_body).to be == {
"@type" => "crons",
"@href" => "/v3/repo/#{repo.id}/crons",
"@representation" => "standard",
"@pagination" => {
"limit" => 25,
"offset" => 0,
"count" => 1,
"is_first" => true,
"is_last" => true,
"next" => nil,
"prev" => nil,
"first" => {
"@href" => "/v3/repo/#{repo.id}/crons",
"offset" => 0,
"limit" => 25},
"last" => {
"@href" => "/v3/repo/#{repo.id}/crons",
"offset" => 0,
"limit" => 25 }},
"crons" => [
{
"@type" => "cron",
"@href" => "/v3/cron/#{cron.id}",
"@representation" => "standard",
"@permissions" => {
"read" => true,
"delete" => false,
"start" => true },
"id" => cron.id,
"repository" => {
"@type" => "repository",
"@href" => "/v3/repo/#{repo.id}",
"@representation" => "minimal",
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal" },
"branch" => {
"@type" => "branch",
"@href" => "/v3/repo/#{repo.id}/branch/#{branch.name}",
"@representation" => "minimal",
"name" => "#{branch.name}" },
"interval" => "daily",
"disable_by_build" => true,
"next_enqueuing" => cron.next_enqueuing.strftime('%Y-%m-%dT%H:%M:%SZ')
}
]
}}
end
describe "fetching crons on a non-existing repository by slug" do
before { get("/v3/repo/svenfuchs%2Fminimal1/crons") }
example { expect(last_response).to be_not_found }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "repository not found (or insufficient access)",
"resource_type" => "repository"
}}
end
describe "fetching crons from private repo, not authenticated" do
before { repo.update_attribute(:private, true) }
before { get("/v3/repo/#{repo.id}/crons") }
after { repo.update_attribute(:private, false) }
example { expect(last_response).to be_not_found }
example { expect(parsed_body).to be == {
"@type" => "error",
"error_type" => "not_found",
"error_message" => "repository not found (or insufficient access)",
"resource_type" => "repository"
}}
end
end

View File

@ -66,7 +66,8 @@ describe Travis::API::V3::Services::Owner::Find do
"disable" => false,
"star" => false,
"unstar" => false,
"create_request" => false},
"create_request" => false,
"create_cron" => false},
"id" => repo.id,
"name" => "example-repo",
"slug" => "example-org/example-repo",
@ -113,7 +114,8 @@ describe Travis::API::V3::Services::Owner::Find do
"disable" => false,
"star" => false,
"unstar" => false,
"create_request"=> false},
"create_request"=> false,
"create_cron" => false},
"id" => repo.id,
"name" => "example-repo",
"slug" => "example-org/example-repo",

View File

@ -44,7 +44,8 @@ describe Travis::API::V3::Services::Repositories::ForCurrentUser do
"disable" => true,
"star" => true,
"unstar" => true,
"create_request" => true},
"create_request" => true,
"create_cron" => false},
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal",

View File

@ -45,7 +45,8 @@ describe Travis::API::V3::Services::Repositories::ForOwner do
"disable" => false,
"star" => false,
"unstar" => false,
"create_request" => false},
"create_request" => false,
"create_cron" => false},
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal",
@ -121,7 +122,8 @@ describe Travis::API::V3::Services::Repositories::ForOwner do
"disable" => false,
"star" => false,
"unstar" => false,
"create_request"=> false },
"create_request"=> false,
"create_cron" => false },
"id" => 1,
"name" => "minimal",
"slug" => "svenfuchs/minimal",
@ -149,7 +151,8 @@ describe Travis::API::V3::Services::Repositories::ForOwner do
"disable" => false,
"star" => false,
"unstar" => false,
"create_request"=> false },
"create_request"=> false,
"create_cron" => false },
"id" => repo2.id,
"name" => "maximal",
"slug" => "svenfuchs/maximal",

View File

@ -36,7 +36,8 @@ describe Travis::API::V3::Services::Repository::Find do
"disable" => false,
"star" => false,
"unstar" => false,
"create_request" => false},
"create_request" => false,
"create_cron" => false},
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal",
@ -113,7 +114,8 @@ describe Travis::API::V3::Services::Repository::Find do
"disable" => false,
"star" => false,
"unstar" => false,
"create_request" => false},
"create_request" => false,
"create_cron" => false},
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal",
@ -150,7 +152,7 @@ describe Travis::API::V3::Services::Repository::Find do
}}
end
describe "private repository, authenticated as internal application with full access" do
describe "private repository without cron feature, authenticated as internal application with full access" do
let(:app_name) { 'travis-example' }
let(:app_secret) { '12345678' }
let(:sign_opts) { "a=#{app_name}" }
@ -175,7 +177,8 @@ describe Travis::API::V3::Services::Repository::Find do
"disable" => true,
"star" => true,
"unstar" => true,
"create_request" => true},
"create_request" => true,
"create_cron" => false},
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal",
@ -218,7 +221,7 @@ describe Travis::API::V3::Services::Repository::Find do
}}
end
describe "private repository, authenticated as internal application with full access, scoped to the right org" do
describe "private repository without cron feature, authenticated as internal application with full access, scoped to the right org" do
let(:app_name) { 'travis-example' }
let(:app_secret) { '12345678' }
let(:sign_opts) { "a=#{app_name}:s=#{repo.owner_name}" }
@ -243,7 +246,8 @@ describe Travis::API::V3::Services::Repository::Find do
"disable" => true,
"star" => true,
"unstar" => true,
"create_request" => true},
"create_request" => true,
"create_cron" => false},
"id" => repo.id,
"name" => "minimal",
"slug" => "svenfuchs/minimal",