imbriaco-integrity 0.1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +12 -0
- data/CHANGES +36 -0
- data/README.markdown +79 -0
- data/Rakefile +79 -0
- data/bin/integrity +4 -0
- data/config/config.sample.ru +21 -0
- data/config/config.sample.yml +41 -0
- data/config/heroku/.gems +1 -0
- data/config/heroku/Rakefile +6 -0
- data/config/heroku/config.ru +7 -0
- data/config/heroku/integrity-config.rb +14 -0
- data/config/thin.sample.yml +13 -0
- data/integrity.gemspec +140 -0
- data/lib/integrity/app.rb +138 -0
- data/lib/integrity/author.rb +39 -0
- data/lib/integrity/build.rb +91 -0
- data/lib/integrity/commit.rb +63 -0
- data/lib/integrity/core_ext/object.rb +6 -0
- data/lib/integrity/helpers/authorization.rb +33 -0
- data/lib/integrity/helpers/breadcrumbs.rb +20 -0
- data/lib/integrity/helpers/forms.rb +29 -0
- data/lib/integrity/helpers/pretty_output.rb +45 -0
- data/lib/integrity/helpers/rendering.rb +25 -0
- data/lib/integrity/helpers/resources.rb +19 -0
- data/lib/integrity/helpers/urls.rb +59 -0
- data/lib/integrity/helpers.rb +16 -0
- data/lib/integrity/installer.rb +136 -0
- data/lib/integrity/migrations.rb +151 -0
- data/lib/integrity/notifier/base.rb +74 -0
- data/lib/integrity/notifier/test/fixtures.rb +108 -0
- data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
- data/lib/integrity/notifier/test.rb +59 -0
- data/lib/integrity/notifier.rb +34 -0
- data/lib/integrity/project/deprecated.rb +17 -0
- data/lib/integrity/project/notifiers.rb +33 -0
- data/lib/integrity/project/push.rb +44 -0
- data/lib/integrity/project.rb +102 -0
- data/lib/integrity/project_builder.rb +56 -0
- data/lib/integrity/scm/git/uri.rb +57 -0
- data/lib/integrity/scm/git.rb +84 -0
- data/lib/integrity/scm.rb +19 -0
- data/lib/integrity.rb +101 -0
- data/public/buttons.css +82 -0
- data/public/reset.css +7 -0
- data/public/spinner.gif +0 -0
- data/test/acceptance/api_test.rb +97 -0
- data/test/acceptance/browse_project_builds_test.rb +65 -0
- data/test/acceptance/browse_project_test.rb +99 -0
- data/test/acceptance/build_notifications_test.rb +95 -0
- data/test/acceptance/create_project_test.rb +97 -0
- data/test/acceptance/delete_project_test.rb +53 -0
- data/test/acceptance/edit_project_test.rb +117 -0
- data/test/acceptance/error_page_test.rb +18 -0
- data/test/acceptance/installer_test.rb +79 -0
- data/test/acceptance/manual_build_project_test.rb +82 -0
- data/test/acceptance/not_found_page_test.rb +29 -0
- data/test/acceptance/project_syndication_test.rb +30 -0
- data/test/acceptance/stylesheet_test.rb +26 -0
- data/test/acceptance/unauthorized_page_test.rb +20 -0
- data/test/helpers/acceptance/email_notifier.rb +55 -0
- data/test/helpers/acceptance/git_helper.rb +99 -0
- data/test/helpers/acceptance/notifier_helper.rb +47 -0
- data/test/helpers/acceptance/textfile_notifier.rb +26 -0
- data/test/helpers/acceptance.rb +79 -0
- data/test/helpers/expectations/be_a.rb +23 -0
- data/test/helpers/expectations/change.rb +90 -0
- data/test/helpers/expectations/have.rb +105 -0
- data/test/helpers/expectations/predicates.rb +37 -0
- data/test/helpers/expectations.rb +4 -0
- data/test/helpers/fixtures.rb +87 -0
- data/test/helpers/initial_migration_fixture.sql +44 -0
- data/test/helpers.rb +87 -0
- data/test/unit/build_test.rb +86 -0
- data/test/unit/commit_test.rb +62 -0
- data/test/unit/helpers_test.rb +103 -0
- data/test/unit/integrity_test.rb +52 -0
- data/test/unit/migrations_test.rb +57 -0
- data/test/unit/notifier/base_test.rb +43 -0
- data/test/unit/notifier/test_test.rb +29 -0
- data/test/unit/notifier_test.rb +85 -0
- data/test/unit/project_builder_test.rb +118 -0
- data/test/unit/project_test.rb +371 -0
- data/test/unit/scm_test.rb +54 -0
- data/views/_commit_info.haml +24 -0
- data/views/build.haml +2 -0
- data/views/error.haml +37 -0
- data/views/home.haml +21 -0
- data/views/integrity.sass +400 -0
- data/views/layout.haml +29 -0
- data/views/new.haml +50 -0
- data/views/not_found.haml +31 -0
- data/views/notifier.haml +7 -0
- data/views/project.builder +21 -0
- data/views/project.haml +30 -0
- data/views/unauthorized.haml +38 -0
- metadata +327 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Integrity
|
4
|
+
class ProjectBuilder
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_accessor :project, :scm
|
8
|
+
def_delegators :project, :name, :uri, :command, :branch
|
9
|
+
|
10
|
+
def self.build(commit)
|
11
|
+
new(commit.project).build(commit)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.delete_working_directory(project)
|
15
|
+
new(project).delete_code
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(project)
|
19
|
+
@project = project
|
20
|
+
@scm = SCM.new(uri, branch, export_directory)
|
21
|
+
end
|
22
|
+
|
23
|
+
def build(commit)
|
24
|
+
build = commit.build
|
25
|
+
build.start!
|
26
|
+
|
27
|
+
Integrity.log "Building #{commit.identifier} (#{branch}) of #{name} in" +
|
28
|
+
"#{export_directory} using #{scm.name}"
|
29
|
+
|
30
|
+
scm.with_revision(commit.identifier) do
|
31
|
+
Integrity.log "Running `#{command}` in #{scm.working_directory}"
|
32
|
+
|
33
|
+
IO.popen("(cd #{scm.working_directory} && #{command}) 2>&1", "r") {
|
34
|
+
|output| build.output = output.read }
|
35
|
+
build.successful = $?.success?
|
36
|
+
end
|
37
|
+
|
38
|
+
build
|
39
|
+
ensure
|
40
|
+
build.complete!
|
41
|
+
commit.update_attributes(scm.info(commit.identifier) || {})
|
42
|
+
project.notifiers.each { |notifier| notifier.notify_of_build(build) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete_code
|
46
|
+
FileUtils.rm_r export_directory
|
47
|
+
rescue Errno::ENOENT
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def export_directory
|
53
|
+
Integrity.config[:export_directory] / "#{SCM.working_tree_path(uri)}-#{branch}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Integrity
|
2
|
+
module SCM
|
3
|
+
class Git
|
4
|
+
# From the git-pull man page:
|
5
|
+
#
|
6
|
+
# GIT URLS
|
7
|
+
# One of the following notations can be used to name the remote repository:
|
8
|
+
#
|
9
|
+
# rsync://host.xz/path/to/repo.git/
|
10
|
+
# http://host.xz/path/to/repo.git/
|
11
|
+
# git://host.xz/~user/path/to/repo.git/
|
12
|
+
# ssh://[user@]host.xz[:port]/path/to/repo.git/
|
13
|
+
# ssh://[user@]host.xz/path/to/repo.git/
|
14
|
+
# ssh://[user@]host.xz/~user/path/to/repo.git/
|
15
|
+
# ssh://[user@]host.xz/~/path/to/repo.git
|
16
|
+
#
|
17
|
+
# SSH is the default transport protocol over the network. You can optionally
|
18
|
+
# specify which user to log-in as, and an alternate, scp-like syntax is also
|
19
|
+
# supported
|
20
|
+
#
|
21
|
+
# Both syntaxes support username expansion, as does the native git protocol,
|
22
|
+
# but only the former supports port specification. The following three are
|
23
|
+
# identical to the last three above, respectively:
|
24
|
+
#
|
25
|
+
# [user@]host.xz:/path/to/repo.git/
|
26
|
+
# [user@]host.xz:~user/path/to/repo.git/
|
27
|
+
# [user@]host.xz:path/to/repo.git
|
28
|
+
#
|
29
|
+
class URI
|
30
|
+
def initialize(uri_string)
|
31
|
+
@uri = Addressable::URI.parse(uri_string)
|
32
|
+
end
|
33
|
+
|
34
|
+
def working_tree_path
|
35
|
+
strip_extension(path).gsub("/", "-")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def strip_extension(string)
|
41
|
+
uri = Pathname.new(string)
|
42
|
+
if uri.extname.any?
|
43
|
+
uri = Pathname.new(string)
|
44
|
+
string.gsub(Regexp.new("#{uri.extname}\/?"), "")
|
45
|
+
else
|
46
|
+
string
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def path
|
51
|
+
path = @uri.path
|
52
|
+
path.gsub(/\~[a-zA-Z0-9]*\//, "").gsub(/^\//, "")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Integrity
|
2
|
+
module SCM
|
3
|
+
class Git
|
4
|
+
require File.dirname(__FILE__) / "git/uri"
|
5
|
+
|
6
|
+
attr_reader :uri, :branch, :working_directory
|
7
|
+
|
8
|
+
def self.working_tree_path(uri)
|
9
|
+
Git::URI.new(uri).working_tree_path
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(uri, branch, working_directory=nil)
|
13
|
+
@uri = uri.to_s
|
14
|
+
@branch = branch.to_s
|
15
|
+
@working_directory = working_directory
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_revision(revision)
|
19
|
+
fetch_code
|
20
|
+
checkout(revision)
|
21
|
+
yield
|
22
|
+
end
|
23
|
+
|
24
|
+
def name
|
25
|
+
self.class.name.split("::").last
|
26
|
+
end
|
27
|
+
|
28
|
+
def head
|
29
|
+
log "Getting the HEAD of '#{uri}' at '#{branch}'"
|
30
|
+
`git ls-remote --heads #{uri} #{branch} | awk '{print $1}'`.chomp
|
31
|
+
end
|
32
|
+
|
33
|
+
def info(revision)
|
34
|
+
format = %Q(---%n:author: %an <%ae>%n:message: >-%n %s%n:committed_at: %ci%n)
|
35
|
+
YAML.load(`cd #{working_directory} && git show -s --pretty=format:"#{format}" #{revision}`)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def fetch_code
|
41
|
+
clone unless cloned?
|
42
|
+
checkout unless on_branch?
|
43
|
+
pull
|
44
|
+
end
|
45
|
+
|
46
|
+
def clone
|
47
|
+
log "Cloning #{uri} to #{working_directory}"
|
48
|
+
`git clone #{uri} #{working_directory} &>/dev/null`
|
49
|
+
end
|
50
|
+
|
51
|
+
def checkout(treeish=nil)
|
52
|
+
strategy = case
|
53
|
+
when treeish then treeish
|
54
|
+
when local_branches.include?(branch) then branch
|
55
|
+
else "origin/#{branch}"
|
56
|
+
end
|
57
|
+
|
58
|
+
log "Checking-out #{strategy}"
|
59
|
+
`cd #{working_directory} && git reset --hard #{strategy} &>/dev/null`
|
60
|
+
end
|
61
|
+
|
62
|
+
def pull
|
63
|
+
log "Pull-ing in #{working_directory}"
|
64
|
+
`cd #{working_directory} && git pull &>/dev/null`
|
65
|
+
end
|
66
|
+
|
67
|
+
def local_branches
|
68
|
+
`cd #{working_directory} && git branch`.split("\n").map {|b| b.delete("*").strip }
|
69
|
+
end
|
70
|
+
|
71
|
+
def cloned?
|
72
|
+
File.directory?(working_directory / ".git")
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_branch?
|
76
|
+
File.basename(`cd #{working_directory} && git symbolic-ref HEAD &>/dev/null`).chomp == branch
|
77
|
+
end
|
78
|
+
|
79
|
+
def log(message)
|
80
|
+
Integrity.log("Git") { message }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Integrity
|
2
|
+
module SCM
|
3
|
+
class SCMUnknownError < StandardError; end
|
4
|
+
|
5
|
+
def self.new(uri, *args)
|
6
|
+
scm_class_for(uri).new(uri, *args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.working_tree_path(uri)
|
10
|
+
scm_class_for(uri).working_tree_path(uri)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def self.scm_class_for(uri)
|
15
|
+
return Git if uri.scheme == "git" || uri.path =~ /\.git\/?/
|
16
|
+
raise SCMUnknownError, "could not find any SCM based on URI '#{uri.to_s}'"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/integrity.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "haml"
|
5
|
+
require "dm-core"
|
6
|
+
require "dm-validations"
|
7
|
+
require "dm-types"
|
8
|
+
require "dm-timestamps"
|
9
|
+
require "dm-aggregates"
|
10
|
+
require "sinatra/base"
|
11
|
+
|
12
|
+
require "yaml"
|
13
|
+
require "logger"
|
14
|
+
require "digest/sha1"
|
15
|
+
require "timeout"
|
16
|
+
require "ostruct"
|
17
|
+
require "pathname"
|
18
|
+
|
19
|
+
require "integrity/core_ext/object"
|
20
|
+
|
21
|
+
require "integrity/project"
|
22
|
+
require "integrity/author"
|
23
|
+
require "integrity/commit"
|
24
|
+
require "integrity/build"
|
25
|
+
require "integrity/project_builder"
|
26
|
+
require "integrity/scm"
|
27
|
+
require "integrity/scm/git"
|
28
|
+
require "integrity/notifier"
|
29
|
+
require "integrity/helpers"
|
30
|
+
require "integrity/app"
|
31
|
+
|
32
|
+
# http://github.com/datamapper/dm-more/commit/13bbcc8592fc391d03d12383d1da6c280c33132a
|
33
|
+
# http://integrity.lighthouseapp.com/projects/14308/tickets/92
|
34
|
+
# http://datamapper.lighthouseapp.com/projects/20609/tickets/802
|
35
|
+
class DataMapper::Types::URI
|
36
|
+
size 255
|
37
|
+
end
|
38
|
+
|
39
|
+
module Integrity
|
40
|
+
def self.new(config=nil)
|
41
|
+
if config.is_a?(String) && File.file?(config)
|
42
|
+
self.config = YAML.load_file(config)
|
43
|
+
elsif config.is_a?(Hash)
|
44
|
+
self.config = config
|
45
|
+
end
|
46
|
+
|
47
|
+
DataMapper.setup(:default, self.config[:database_uri])
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.register_notifier(klass)
|
51
|
+
raise ArgumentError unless valid_notifier?(klass)
|
52
|
+
|
53
|
+
self.notifiers[klass.to_s.split(":").last] = klass
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.notifiers
|
57
|
+
@notifiers ||= {}
|
58
|
+
@notifiers
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.default_configuration
|
62
|
+
@defaults ||= { :database_uri => "sqlite3::memory:",
|
63
|
+
:export_directory => "/tmp/exports",
|
64
|
+
:log => STDOUT,
|
65
|
+
:base_uri => "http://localhost:8910",
|
66
|
+
:use_basic_auth => false,
|
67
|
+
:build_all_commits => true,
|
68
|
+
:log_debug_info => false }
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.config
|
72
|
+
@config ||= default_configuration.dup
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.config=(options)
|
76
|
+
@config = default_configuration.merge(options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.log(message, &block)
|
80
|
+
logger.info(message, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.logger
|
84
|
+
@logger ||= Logger.new(config[:log], "daily").tap do |logger|
|
85
|
+
logger.formatter = LogFormatter.new
|
86
|
+
end
|
87
|
+
end
|
88
|
+
private_class_method :logger
|
89
|
+
|
90
|
+
def self.valid_notifier?(notifier)
|
91
|
+
notifier.respond_to?(:to_haml) && notifier.respond_to?(:notify_of_build) &&
|
92
|
+
notifier != Notifier::Base
|
93
|
+
end
|
94
|
+
private_class_method :valid_notifier?
|
95
|
+
|
96
|
+
class LogFormatter < Logger::Formatter
|
97
|
+
def call(severity, time, progname, msg)
|
98
|
+
time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/public/buttons.css
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
/* --------------------------------------------------------------
|
2
|
+
|
3
|
+
buttons.css
|
4
|
+
* Gives you some great CSS-only buttons.
|
5
|
+
|
6
|
+
Created by Kevin Hale [particletree.com]
|
7
|
+
* particletree.com/features/rediscovering-the-button-element
|
8
|
+
|
9
|
+
See Readme.txt in this folder for instructions.
|
10
|
+
|
11
|
+
-------------------------------------------------------------- */
|
12
|
+
|
13
|
+
button {
|
14
|
+
display:block;
|
15
|
+
float:left;
|
16
|
+
margin:0 0.583em 0.667em 0;
|
17
|
+
padding:5px 10px 5px 7px; /* Links */
|
18
|
+
|
19
|
+
border:1px solid #dedede;
|
20
|
+
border-top:1px solid #eee;
|
21
|
+
border-left:1px solid #eee;
|
22
|
+
|
23
|
+
background-color:#f5f5f5;
|
24
|
+
font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
|
25
|
+
font-size:100%;
|
26
|
+
line-height:130%;
|
27
|
+
text-decoration:none;
|
28
|
+
font-weight:bold;
|
29
|
+
color:#565656;
|
30
|
+
cursor:pointer;
|
31
|
+
}
|
32
|
+
button {
|
33
|
+
width:auto;
|
34
|
+
overflow:visible;
|
35
|
+
padding:4px 10px 3px 7px; /* IE6 */
|
36
|
+
}
|
37
|
+
button[type] {
|
38
|
+
padding:4px 10px 4px 7px; /* Firefox */
|
39
|
+
line-height:17px; /* Safari */
|
40
|
+
}
|
41
|
+
*:first-child+html button[type] {
|
42
|
+
padding:4px 10px 3px 7px; /* IE7 */
|
43
|
+
}
|
44
|
+
button img {
|
45
|
+
margin:0 3px -3px 0 !important;
|
46
|
+
padding:0;
|
47
|
+
border:none;
|
48
|
+
width:16px;
|
49
|
+
height:16px;
|
50
|
+
float:none;
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
/* Button colors
|
55
|
+
-------------------------------------------------------------- */
|
56
|
+
|
57
|
+
/* Standard */
|
58
|
+
button:hover {
|
59
|
+
background-color:#dff4ff;
|
60
|
+
border:1px solid #c2e1ef;
|
61
|
+
color:#336699;
|
62
|
+
}
|
63
|
+
|
64
|
+
/* Positive */
|
65
|
+
body .positive {
|
66
|
+
color:#529214;
|
67
|
+
}
|
68
|
+
button.positive:hover {
|
69
|
+
background-color:#E6EFC2;
|
70
|
+
border:1px solid #C6D880;
|
71
|
+
color:#529214;
|
72
|
+
}
|
73
|
+
|
74
|
+
/* Negative */
|
75
|
+
body .negative {
|
76
|
+
color:#d12f19;
|
77
|
+
}
|
78
|
+
button.negative:hover {
|
79
|
+
background:#fbe3e4;
|
80
|
+
border:1px solid #fbc2c4;
|
81
|
+
color:#d12f19;
|
82
|
+
}
|
data/public/reset.css
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
/*
|
2
|
+
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
|
3
|
+
Code licensed under the BSD License:
|
4
|
+
http://developer.yahoo.net/yui/license.txt
|
5
|
+
version: 2.5.2
|
6
|
+
*/
|
7
|
+
html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
|
data/public/spinner.gif
ADDED
Binary file
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../helpers/acceptance"
|
2
|
+
|
3
|
+
class ApiTest < Test::Unit::AcceptanceTestCase
|
4
|
+
story <<-EOF
|
5
|
+
As a project owner,
|
6
|
+
I want to be able to use GitHub as a build triggerer
|
7
|
+
So that my project is built everytime I push to the Holy Hub
|
8
|
+
EOF
|
9
|
+
|
10
|
+
def payload(after, branch="master", commits=[])
|
11
|
+
payload = { "after" => "#{after}", "ref" => "refs/heads/#{branch}" }
|
12
|
+
payload["commits"] = commits if commits.any?
|
13
|
+
payload.to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
def post(path, data)
|
17
|
+
request_page(path, "post", data)
|
18
|
+
end
|
19
|
+
|
20
|
+
scenario "it only build commits for the branch being monitored" do
|
21
|
+
repo = git_repo(:my_test_project) # initial commit && successful commit
|
22
|
+
Project.gen(:my_test_project, :uri => repo.path, :branch => "my-branch")
|
23
|
+
|
24
|
+
basic_auth "admin", "test"
|
25
|
+
|
26
|
+
lambda do
|
27
|
+
post "/my-test-project/push", :payload => payload(repo.head, "master", repo.commits)
|
28
|
+
response_code.should == 422
|
29
|
+
end.should_not change(Build, :count)
|
30
|
+
|
31
|
+
visit "/my-test-project"
|
32
|
+
|
33
|
+
assert_contain("No builds for this project")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "receiving a build request with build_all_commits *enabled* builds all commits, most recent first" do
|
37
|
+
Integrity.config[:build_all_commits] = true
|
38
|
+
|
39
|
+
repo = git_repo(:my_test_project) # initial commit && successful commit
|
40
|
+
3.times do |i|
|
41
|
+
repo.add_commit("commit #{i}") do
|
42
|
+
system "echo commit_#{i} >> test-file"
|
43
|
+
system "git add test-file &>/dev/null"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Project.gen(:my_test_project, :uri => repo.path, :command => "echo successful", :branch => "master")
|
48
|
+
|
49
|
+
basic_auth "admin", "test"
|
50
|
+
post "/my-test-project/push", :payload => payload(repo.head, "master", repo.commits)
|
51
|
+
|
52
|
+
visit "/my-test-project"
|
53
|
+
|
54
|
+
assert_have_tag("h1", :content => "Built #{git_repo(:my_test_project).short_head} successfully")
|
55
|
+
assert_have_tag(".attribution", :content => "by John Doe")
|
56
|
+
|
57
|
+
assert_have_tag("#previous_builds li", :count => 4)
|
58
|
+
end
|
59
|
+
|
60
|
+
scenario "receiving a build request with build_all_commits *disabled* only builds the last commit passed" do
|
61
|
+
Integrity.config[:build_all_commits] = false
|
62
|
+
|
63
|
+
Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path)
|
64
|
+
|
65
|
+
git_repo(:my_test_project).add_failing_commit
|
66
|
+
git_repo(:my_test_project).add_successful_commit
|
67
|
+
head = git_repo(:my_test_project).head
|
68
|
+
|
69
|
+
basic_auth "admin", "test"
|
70
|
+
post "/my-test-project/push", :payload => payload(head, "master", git_repo(:my_test_project).commits)
|
71
|
+
|
72
|
+
response_code.should == 201
|
73
|
+
|
74
|
+
visit "/my-test-project"
|
75
|
+
|
76
|
+
assert_have_tag("h1", :content => "Built #{git_repo(:my_test_project).short_head} successfully")
|
77
|
+
|
78
|
+
assert_have_no_tag("#previous_builds li")
|
79
|
+
end
|
80
|
+
|
81
|
+
scenario "an unauthenticated request returns a 401" do
|
82
|
+
Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path)
|
83
|
+
head = git_repo(:my_test_project).head
|
84
|
+
post "/my-test-project/push", :payload => payload(head, "master")
|
85
|
+
|
86
|
+
response_code.should == 401
|
87
|
+
end
|
88
|
+
|
89
|
+
scenario "receiving a build request with an invalid payload returns an Invalid Request error" do
|
90
|
+
Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path)
|
91
|
+
|
92
|
+
basic_auth "admin", "test"
|
93
|
+
|
94
|
+
post "/my-test-project/push", :payload => "foo"
|
95
|
+
response_code.should == 422
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../helpers/acceptance"
|
2
|
+
|
3
|
+
class BrowseProjectBuildsTest < Test::Unit::AcceptanceTestCase
|
4
|
+
story <<-EOS
|
5
|
+
As a user,
|
6
|
+
I want to browse the builds of a project in Integrity
|
7
|
+
So I can see the history of a project
|
8
|
+
EOS
|
9
|
+
|
10
|
+
scenario "a project with no builds should say so in a friendly manner" do
|
11
|
+
Project.gen(:integrity, :public => true, :commits => [])
|
12
|
+
|
13
|
+
visit "/integrity"
|
14
|
+
|
15
|
+
assert_have_no_tag("#last_build")
|
16
|
+
assert_have_no_tag("#previous_builds")
|
17
|
+
assert_contain("No builds for this project, buddy")
|
18
|
+
end
|
19
|
+
|
20
|
+
scenario "a user can see the last build and the list of previous builds on a project page" do
|
21
|
+
Project.gen(:integrity, :public => true, :commits => \
|
22
|
+
3.of { Commit.gen(:successful) } +
|
23
|
+
2.of { Commit.gen(:failed) } +
|
24
|
+
2.of { Commit.gen(:pending) })
|
25
|
+
|
26
|
+
visit "/integrity"
|
27
|
+
|
28
|
+
assert_have_tag("#last_build")
|
29
|
+
|
30
|
+
within("ul#previous_builds") do
|
31
|
+
assert_have_tag("li.pending", :count => 2)
|
32
|
+
assert_have_tag("li.failed", :count => 2)
|
33
|
+
assert_have_tag("li.success", :count => 2)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
scenario "a user can see details about the last build on the project page" do
|
38
|
+
commit = Commit.gen(:successful, :identifier => "7fee3f0014b529e2b76d591a8085d76eab0ff923",
|
39
|
+
:author => "Nicolas Sanguinetti <contacto@nicolassanguinetti.info>",
|
40
|
+
:message => "No more pending tests :)",
|
41
|
+
:committed_at => Time.mktime(2008, 12, 15, 18))
|
42
|
+
commit.build.update_attributes(:output => "This is the build output")
|
43
|
+
Project.gen(:integrity, :public => true, :commits => [commit])
|
44
|
+
|
45
|
+
visit "/integrity"
|
46
|
+
|
47
|
+
assert_have_tag("h1", :content => "Built 7fee3f0 successfully")
|
48
|
+
assert_have_tag("blockquote p", :content => "No more pending tests")
|
49
|
+
assert_have_tag("span.who", :content => "by: Nicolas Sanguinetti")
|
50
|
+
assert_have_tag("span.when", :content => "Dec 15th")
|
51
|
+
assert_have_tag("pre.output", :content => "This is the build output")
|
52
|
+
end
|
53
|
+
|
54
|
+
scenario "a user can browse to individual build pages" do
|
55
|
+
Project.gen(:integrity, :public => true, :commits => [
|
56
|
+
Commit.gen(:successful, :identifier => "7fee3f0014b529e2b76d591a8085d76eab0ff923"),
|
57
|
+
Commit.gen(:successful, :identifier => "87e673a83d273ecde121624a3fcfae57a04f2b76")
|
58
|
+
])
|
59
|
+
|
60
|
+
visit "/integrity"
|
61
|
+
click_link(/Build 87e673a/)
|
62
|
+
|
63
|
+
assert_have_tag("h1", :content => "Built 87e673a successfully")
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../helpers/acceptance"
|
2
|
+
|
3
|
+
class BrowsePublicProjectsTest < Test::Unit::AcceptanceTestCase
|
4
|
+
story <<-EOS
|
5
|
+
As a user,
|
6
|
+
I want to browse public projects on Integrity,
|
7
|
+
So I can follow the status of my favorite OSS projects
|
8
|
+
EOS
|
9
|
+
|
10
|
+
scenario "a user can see a public project listed on the home page" do
|
11
|
+
Project.gen(:integrity, :public => true)
|
12
|
+
Project.gen(:my_test_project, :public => true)
|
13
|
+
|
14
|
+
visit "/"
|
15
|
+
|
16
|
+
assert_have_tag("a", :content => "Integrity")
|
17
|
+
assert_have_tag("a", :content => "My Test Project")
|
18
|
+
end
|
19
|
+
|
20
|
+
scenario "a user can't see a private project listed on the home page" do
|
21
|
+
Project.gen(:my_test_project, :public => false)
|
22
|
+
Project.gen(:integrity, :public => true)
|
23
|
+
|
24
|
+
visit "/"
|
25
|
+
|
26
|
+
assert_have_no_tag("a", :content => "My Test Project")
|
27
|
+
assert_have_tag("a", :content => "Integrity")
|
28
|
+
end
|
29
|
+
|
30
|
+
scenario "a user can see the projects status on the home page" do
|
31
|
+
integrity = Project.gen(:integrity, :commits => 3.of { Commit.gen(:successful) })
|
32
|
+
test = Project.gen(:my_test_project, :commits => 2.of { Commit.gen(:failed) })
|
33
|
+
no_build = Project.gen(:public => true, :building => false)
|
34
|
+
building = Project.gen(:public => true, :building => true)
|
35
|
+
|
36
|
+
visit "/"
|
37
|
+
|
38
|
+
assert_contain("Built #{integrity.last_commit.short_identifier} successfully")
|
39
|
+
assert_contain("Built #{test.last_commit.short_identifier} and failed")
|
40
|
+
assert_contain("Never built yet")
|
41
|
+
assert_contain("Building!")
|
42
|
+
end
|
43
|
+
|
44
|
+
scenario "a user clicking through a link on the home page for a public project arrives at the project page" do
|
45
|
+
Project.gen(:my_test_project, :public => true)
|
46
|
+
|
47
|
+
visit "/"
|
48
|
+
click_link "My Test Project"
|
49
|
+
|
50
|
+
assert_have_tag("h1", :content => "My Test Project")
|
51
|
+
|
52
|
+
# He can then go back to the project listing
|
53
|
+
click_link "projects"
|
54
|
+
assert_have_tag("a", :content => "My Test Project")
|
55
|
+
end
|
56
|
+
|
57
|
+
scenario "a user gets a 404 when browsing to an unexisting project" do
|
58
|
+
visit "/who-are-you"
|
59
|
+
|
60
|
+
response_code.should == 404
|
61
|
+
assert_have_tag("h1", :content => "you seem a bit lost, sir")
|
62
|
+
end
|
63
|
+
|
64
|
+
scenario "a user browsing to the url of a private project gets a 401" do
|
65
|
+
Project.gen(:my_test_project, :public => false)
|
66
|
+
|
67
|
+
visit "/my-test-project"
|
68
|
+
|
69
|
+
response_code.should == 401
|
70
|
+
assert_have_tag("h1", :content => "know the password?")
|
71
|
+
end
|
72
|
+
|
73
|
+
scenario "an admin can browse to a private project just fine" do
|
74
|
+
Project.gen(:my_test_project, :public => false)
|
75
|
+
|
76
|
+
login_as "admin", "test"
|
77
|
+
|
78
|
+
visit "/"
|
79
|
+
click_link "My Test Project"
|
80
|
+
|
81
|
+
assert_have_tag("h1", :content => "My Test Project")
|
82
|
+
end
|
83
|
+
|
84
|
+
scenario "a user browsing to a public project with no build see a friendly message" do
|
85
|
+
project = Project.gen(:my_test_project, :public => true)
|
86
|
+
|
87
|
+
visit "/my-test-project"
|
88
|
+
assert_contain("No builds for this project, buddy")
|
89
|
+
end
|
90
|
+
|
91
|
+
scenario "an admin browsing to a private project with no build see a friendly message" do
|
92
|
+
Project.gen(:my_test_project, :public => false)
|
93
|
+
|
94
|
+
login_as "admin", "test"
|
95
|
+
visit "/my-test-project"
|
96
|
+
|
97
|
+
assert_contain("No builds for this project, buddy")
|
98
|
+
end
|
99
|
+
end
|