imbriaco-integrity 0.1.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/.gitignore +12 -0
  2. data/CHANGES +36 -0
  3. data/README.markdown +79 -0
  4. data/Rakefile +79 -0
  5. data/bin/integrity +4 -0
  6. data/config/config.sample.ru +21 -0
  7. data/config/config.sample.yml +41 -0
  8. data/config/heroku/.gems +1 -0
  9. data/config/heroku/Rakefile +6 -0
  10. data/config/heroku/config.ru +7 -0
  11. data/config/heroku/integrity-config.rb +14 -0
  12. data/config/thin.sample.yml +13 -0
  13. data/integrity.gemspec +140 -0
  14. data/lib/integrity/app.rb +138 -0
  15. data/lib/integrity/author.rb +39 -0
  16. data/lib/integrity/build.rb +91 -0
  17. data/lib/integrity/commit.rb +63 -0
  18. data/lib/integrity/core_ext/object.rb +6 -0
  19. data/lib/integrity/helpers/authorization.rb +33 -0
  20. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  21. data/lib/integrity/helpers/forms.rb +29 -0
  22. data/lib/integrity/helpers/pretty_output.rb +45 -0
  23. data/lib/integrity/helpers/rendering.rb +25 -0
  24. data/lib/integrity/helpers/resources.rb +19 -0
  25. data/lib/integrity/helpers/urls.rb +59 -0
  26. data/lib/integrity/helpers.rb +16 -0
  27. data/lib/integrity/installer.rb +136 -0
  28. data/lib/integrity/migrations.rb +151 -0
  29. data/lib/integrity/notifier/base.rb +74 -0
  30. data/lib/integrity/notifier/test/fixtures.rb +108 -0
  31. data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
  32. data/lib/integrity/notifier/test.rb +59 -0
  33. data/lib/integrity/notifier.rb +34 -0
  34. data/lib/integrity/project/deprecated.rb +17 -0
  35. data/lib/integrity/project/notifiers.rb +33 -0
  36. data/lib/integrity/project/push.rb +44 -0
  37. data/lib/integrity/project.rb +102 -0
  38. data/lib/integrity/project_builder.rb +56 -0
  39. data/lib/integrity/scm/git/uri.rb +57 -0
  40. data/lib/integrity/scm/git.rb +84 -0
  41. data/lib/integrity/scm.rb +19 -0
  42. data/lib/integrity.rb +101 -0
  43. data/public/buttons.css +82 -0
  44. data/public/reset.css +7 -0
  45. data/public/spinner.gif +0 -0
  46. data/test/acceptance/api_test.rb +97 -0
  47. data/test/acceptance/browse_project_builds_test.rb +65 -0
  48. data/test/acceptance/browse_project_test.rb +99 -0
  49. data/test/acceptance/build_notifications_test.rb +95 -0
  50. data/test/acceptance/create_project_test.rb +97 -0
  51. data/test/acceptance/delete_project_test.rb +53 -0
  52. data/test/acceptance/edit_project_test.rb +117 -0
  53. data/test/acceptance/error_page_test.rb +18 -0
  54. data/test/acceptance/installer_test.rb +79 -0
  55. data/test/acceptance/manual_build_project_test.rb +82 -0
  56. data/test/acceptance/not_found_page_test.rb +29 -0
  57. data/test/acceptance/project_syndication_test.rb +30 -0
  58. data/test/acceptance/stylesheet_test.rb +26 -0
  59. data/test/acceptance/unauthorized_page_test.rb +20 -0
  60. data/test/helpers/acceptance/email_notifier.rb +55 -0
  61. data/test/helpers/acceptance/git_helper.rb +99 -0
  62. data/test/helpers/acceptance/notifier_helper.rb +47 -0
  63. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  64. data/test/helpers/acceptance.rb +79 -0
  65. data/test/helpers/expectations/be_a.rb +23 -0
  66. data/test/helpers/expectations/change.rb +90 -0
  67. data/test/helpers/expectations/have.rb +105 -0
  68. data/test/helpers/expectations/predicates.rb +37 -0
  69. data/test/helpers/expectations.rb +4 -0
  70. data/test/helpers/fixtures.rb +87 -0
  71. data/test/helpers/initial_migration_fixture.sql +44 -0
  72. data/test/helpers.rb +87 -0
  73. data/test/unit/build_test.rb +86 -0
  74. data/test/unit/commit_test.rb +62 -0
  75. data/test/unit/helpers_test.rb +103 -0
  76. data/test/unit/integrity_test.rb +52 -0
  77. data/test/unit/migrations_test.rb +57 -0
  78. data/test/unit/notifier/base_test.rb +43 -0
  79. data/test/unit/notifier/test_test.rb +29 -0
  80. data/test/unit/notifier_test.rb +85 -0
  81. data/test/unit/project_builder_test.rb +118 -0
  82. data/test/unit/project_test.rb +371 -0
  83. data/test/unit/scm_test.rb +54 -0
  84. data/views/_commit_info.haml +24 -0
  85. data/views/build.haml +2 -0
  86. data/views/error.haml +37 -0
  87. data/views/home.haml +21 -0
  88. data/views/integrity.sass +400 -0
  89. data/views/layout.haml +29 -0
  90. data/views/new.haml +50 -0
  91. data/views/not_found.haml +31 -0
  92. data/views/notifier.haml +7 -0
  93. data/views/project.builder +21 -0
  94. data/views/project.haml +30 -0
  95. data/views/unauthorized.haml +38 -0
  96. 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
@@ -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;}
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