integrity 0.1.8 → 0.1.9.0

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.
Files changed (77) hide show
  1. data/README.markdown +7 -0
  2. data/Rakefile +77 -124
  3. data/config/config.ru +29 -0
  4. data/config/config.sample.ru +6 -16
  5. data/config/config.sample.yml +15 -12
  6. data/config/config.yml +34 -0
  7. data/lib/integrity.rb +13 -13
  8. data/lib/integrity/app.rb +138 -0
  9. data/lib/integrity/author.rb +39 -0
  10. data/lib/integrity/build.rb +54 -31
  11. data/lib/integrity/commit.rb +71 -0
  12. data/lib/integrity/helpers.rb +3 -3
  13. data/lib/integrity/helpers/authorization.rb +2 -2
  14. data/lib/integrity/helpers/forms.rb +3 -3
  15. data/lib/integrity/helpers/pretty_output.rb +1 -1
  16. data/lib/integrity/helpers/rendering.rb +6 -1
  17. data/lib/integrity/helpers/resources.rb +9 -3
  18. data/lib/integrity/helpers/urls.rb +15 -13
  19. data/lib/integrity/installer.rb +43 -60
  20. data/lib/integrity/migrations.rb +31 -48
  21. data/lib/integrity/notifier.rb +14 -16
  22. data/lib/integrity/notifier/base.rb +29 -19
  23. data/lib/integrity/notifier/test_helpers.rb +100 -0
  24. data/lib/integrity/project.rb +69 -33
  25. data/lib/integrity/project_builder.rb +23 -14
  26. data/lib/integrity/scm/git.rb +15 -14
  27. data/lib/integrity/scm/git/uri.rb +9 -9
  28. data/test/acceptance/api_test.rb +97 -0
  29. data/test/acceptance/browse_project_builds_test.rb +65 -0
  30. data/test/acceptance/browse_project_test.rb +95 -0
  31. data/test/acceptance/build_notifications_test.rb +42 -0
  32. data/test/acceptance/create_project_test.rb +97 -0
  33. data/test/acceptance/delete_project_test.rb +53 -0
  34. data/test/acceptance/edit_project_test.rb +117 -0
  35. data/test/acceptance/error_page_test.rb +18 -0
  36. data/test/acceptance/helpers.rb +2 -0
  37. data/test/acceptance/installer_test.rb +62 -0
  38. data/test/acceptance/manual_build_project_test.rb +82 -0
  39. data/test/acceptance/notifier_test.rb +109 -0
  40. data/test/acceptance/project_syndication_test.rb +30 -0
  41. data/test/acceptance/stylesheet_test.rb +18 -0
  42. data/test/helpers.rb +59 -26
  43. data/test/helpers/acceptance.rb +19 -65
  44. data/test/helpers/acceptance/email_notifier.rb +55 -0
  45. data/test/helpers/acceptance/git_helper.rb +15 -15
  46. data/test/helpers/acceptance/textfile_notifier.rb +3 -3
  47. data/test/helpers/expectations.rb +0 -1
  48. data/test/helpers/expectations/be_a.rb +4 -4
  49. data/test/helpers/expectations/change.rb +5 -5
  50. data/test/helpers/expectations/have.rb +4 -4
  51. data/test/helpers/expectations/predicates.rb +4 -4
  52. data/test/helpers/fixtures.rb +44 -18
  53. data/test/helpers/initial_migration_fixture.sql +44 -0
  54. data/test/unit/build_test.rb +51 -0
  55. data/test/unit/commit_test.rb +83 -0
  56. data/test/unit/helpers_test.rb +56 -0
  57. data/test/unit/integrity_test.rb +18 -0
  58. data/test/unit/migrations_test.rb +56 -0
  59. data/test/unit/notifier_test.rb +123 -0
  60. data/test/unit/project_builder_test.rb +108 -0
  61. data/test/unit/project_test.rb +282 -0
  62. data/test/unit/scm_test.rb +54 -0
  63. data/views/_commit_info.haml +24 -0
  64. data/views/build.haml +2 -2
  65. data/views/error.haml +4 -3
  66. data/views/home.haml +3 -5
  67. data/views/integrity.sass +19 -6
  68. data/views/new.haml +6 -6
  69. data/views/project.builder +9 -9
  70. data/views/project.haml +14 -12
  71. metadata +89 -122
  72. data/VERSION.yml +0 -4
  73. data/app.rb +0 -138
  74. data/integrity.gemspec +0 -76
  75. data/lib/integrity/core_ext/string.rb +0 -5
  76. data/test/helpers/expectations/have_tag.rb +0 -128
  77. data/views/_build_info.haml +0 -18
@@ -2,15 +2,7 @@ module Integrity
2
2
  module Helpers
3
3
  module Urls
4
4
  def url(path)
5
- url = "#{request.scheme}://#{request.host}"
6
-
7
- if request.scheme == "https" && request.port != 443 ||
8
- request.scheme == "http" && request.port != 80
9
- url << ":#{request.port}"
10
- end
11
-
12
- url << "/" unless path.index("/").zero?
13
- url << path
5
+ Addressable::URI.parse(request.url).join(path).to_s
14
6
  end
15
7
 
16
8
  def root_url
@@ -35,13 +27,23 @@ module Integrity
35
27
  end.to_s
36
28
  end
37
29
 
38
- def build_path(build)
39
- "/#{build.project.permalink}/builds/#{build.commit_identifier}"
30
+ def commit_path(commit, *path)
31
+ project_path(commit.project, "commits", commit.identifier, *path)
32
+ end
33
+
34
+ def build_path(build, *path)
35
+ warn "#build_path is deprecated, use #commit_path instead"
36
+ commit_path build.commit, *path
37
+ end
38
+
39
+ def commit_url(commit)
40
+ url commit_path(commit)
40
41
  end
41
42
 
42
43
  def build_url(build)
43
- url build_path(build)
44
+ warn "#build_url is deprecated, use #commit_url instead"
45
+ commit_url build.commit
44
46
  end
45
47
  end
46
48
  end
47
- end
49
+ end
@@ -6,63 +6,73 @@ module Integrity
6
6
  include FileUtils
7
7
 
8
8
  desc "install [PATH]",
9
- "Copy template files to PATH. Next, go there and edit them."
9
+ "Copy template files to PATH for desired deployement strategy
10
+ (either Thin or Passenger). Next, go there and edit them."
11
+ method_options :passenger => false, :thin => false
10
12
  def install(path)
11
- @root = File.expand_path(path)
13
+ @root = Pathname(path).expand_path
12
14
 
13
15
  create_dir_structure
14
16
  copy_template_files
15
17
  edit_template_files
16
- create_db(root / "config.yml")
18
+ migrate_db(root.join("config.yml"))
17
19
  after_setup_message
18
20
  end
19
21
 
20
- desc "create_db [CONFIG]",
21
- "Checks the `database_uri` in CONFIG and creates and bootstraps a database for integrity"
22
- def create_db(config, direction="up")
22
+ desc "migrate_db [CONFIG]",
23
+ "Checks the `database_uri` in CONFIG and migrates the
24
+ database up to the lastest version."
25
+ def migrate_db(config)
23
26
  Integrity.new(config)
24
- migrate_db(direction, 1)
25
- end
26
27
 
27
- desc "version",
28
- "Print the current integrity version"
29
- def version
30
- puts Integrity.version
28
+ require "integrity/migrations"
29
+ Integrity.migrate_db
31
30
  end
32
31
 
33
- private
34
- attr_reader :root
32
+ desc "launch [CONFIG]",
33
+ "Launch Integrity real quick."
34
+ method_options :config => :optional, :port => 4567
35
+ def launch
36
+ require "thin"
37
+ require "do_sqlite3"
38
+
39
+ if File.file?(options[:config].to_s)
40
+ Integrity.new(options[:config])
41
+ else
42
+ DataMapper.setup(:default, "sqlite3::memory:")
43
+ end
35
44
 
36
- def migrate_db(direction, level=nil)
37
- require "integrity/migrations"
45
+ DataMapper.auto_migrate!
38
46
 
39
- set_up_migrations unless migrations_already_set_up?
40
- add_initial_migration if tables_from_before_migrations_exist?
47
+ Thin::Server.start("0.0.0.0", options[:port], Integrity::App)
48
+ rescue LoadError => boom
49
+ missing_dependency = boom.message.split("--").last.lstrip
50
+ puts "Please install #{missing_dependency} to launch Integrity"
51
+ end
41
52
 
42
- case direction
43
- when "up" then Integrity::Migrations.migrate_up!(level)
44
- when "down" then Integrity::Migrations.migrate_down!(level)
45
- else raise ArgumentError, "DIRECTION must be either up or down"
46
- end
47
- end
53
+ private
54
+ attr_reader :root
48
55
 
49
56
  def create_dir_structure
50
57
  mkdir_p root
51
58
  mkdir_p root / "builds"
52
59
  mkdir_p root / "log"
53
- mkdir_p root / "public" # this one is to play nice with Passenger
54
- mkdir_p root / "tmp" # this one is to play nice with Passenger
60
+
61
+ if options[:passenger]
62
+ mkdir_p root / "public"
63
+ mkdir_p root / "tmp"
64
+ end
55
65
  end
56
66
 
57
67
  def copy_template_files
58
- cp Integrity.root / "config" / "config.sample.ru", root / "config.ru"
59
- cp Integrity.root / "config" / "config.sample.yml", root / "config.yml"
60
- cp Integrity.root / "config" / "thin.sample.yml", root / "thin.yml"
68
+ copy "config/config.sample.ru"
69
+ copy "config/config.sample.yml"
70
+ copy "config/thin.sample.yml" if options[:thin]
61
71
  end
62
72
 
63
73
  def edit_template_files
64
74
  edit_integrity_configuration
65
- edit_thin_configuration
75
+ edit_thin_configuration if options[:thin]
66
76
  end
67
77
 
68
78
  def edit_integrity_configuration
@@ -97,36 +107,9 @@ module Integrity
97
107
  puts %Q(Don't forget to tweak #{root / "config.yml"} to your needs.)
98
108
  end
99
109
 
100
- def set_up_migrations
101
- database_adapter.execute %q(CREATE TABLE "migration_info" ("migration_name" VARCHAR(255));)
102
- end
103
-
104
- def add_initial_migration
105
- database_adapter.execute %q(INSERT INTO "migration_info" ("migration_name") VALUES ("initial"))
106
- end
107
-
108
- def tables_from_before_migrations_exist?
109
- table_exists?("integrity_projects") &&
110
- table_exists?("integrity_builds") &&
111
- table_exists?("integrity_notifiers")
112
- end
113
-
114
- def migrations_already_set_up?
115
- table_exists?("migration_info")
116
- end
117
-
118
- def without_pluralizing_table_names
119
- database_adapter.resource_naming_convention = DataMapper::NamingConventions::Resource::Underscored
120
- yield
121
- database_adapter.resource_naming_convention = DataMapper::NamingConventions::Resource::UnderscoredAndPluralized
122
- end
123
-
124
- def table_exists?(table_name)
125
- database_adapter.storage_exists?(table_name)
126
- end
127
-
128
- def database_adapter
129
- DataMapper.repository(:default).adapter
110
+ def copy(path)
111
+ cp(File.dirname(__FILE__) + "/../../#{path}",
112
+ root.join(File.basename(path).gsub(/\.sample/, "")))
130
113
  end
131
114
  end
132
115
  end
@@ -2,13 +2,38 @@ require "dm-migrations"
2
2
  require "migration_runner"
3
3
 
4
4
  module Integrity
5
- class Migrations
6
- include DataMapper::Types
5
+ def self.migrate_db
6
+ setup_initial_migration if pre_migrations?
7
+ Integrity::Migrations.migrate_up!
8
+ end
9
+
10
+ def self.setup_initial_migration
11
+ database_adapter.execute %q(CREATE TABLE "migration_info" ("migration_name" VARCHAR(255));)
12
+ database_adapter.execute %q(INSERT INTO "migration_info" ("migration_name") VALUES ("initial"))
13
+ end
14
+
15
+ def self.pre_migrations?
16
+ !table_exists?("migration_info") &&
17
+ ( table_exists?("integrity_projects") &&
18
+ table_exists?("integrity_builds") &&
19
+ table_exists?("integrity_notifiers") )
20
+ end
21
+
22
+ def self.table_exists?(table_name)
23
+ database_adapter.storage_exists?(table_name)
24
+ end
25
+
26
+ def self.database_adapter
27
+ DataMapper.repository(:default).adapter
28
+ end
29
+
30
+ module Migrations
31
+ # This is what is actually happening:
32
+ # include DataMapper::MigrationRunner
7
33
 
8
- # not strictly necessary, but it makes it clear what is going on.
9
- include DataMapper::MigrationRunner
34
+ include DataMapper::Types
10
35
 
11
- migration 1, :initial, :verbose => false do
36
+ migration 1, :initial, :verbose => true do
12
37
  up do
13
38
  create_table :integrity_projects do
14
39
  column :id, Integer, :serial => true
@@ -46,15 +71,9 @@ module Integrity
46
71
  column :project_id, Integer
47
72
  end
48
73
  end
49
-
50
- down do
51
- drop_table :integrity_notifiers
52
- drop_table :integrity_projects
53
- drop_table :integrity_builds
54
- end
55
74
  end
56
75
 
57
- migration 2, :add_commits, :verbose => false do
76
+ migration 2, :add_commits, :verbose => true do
58
77
  up do
59
78
  class ::Integrity::Build
60
79
  property :commit_identifier, String
@@ -116,42 +135,6 @@ module Integrity
116
135
  :output => build.output)
117
136
  end
118
137
  end
119
-
120
- down do
121
- modify_table :integrity_builds do
122
- add_column :commit_identifier, String, :nullable => false
123
- add_column :commit_metadata, Yaml, :nullable => false
124
- add_column :project_id, Integer
125
- end
126
-
127
- # sqlite hodgepockery
128
- all_builds = Build.all.map {|b| b.freeze }
129
- drop_table :integrity_builds
130
- create_table :integrity_builds do
131
- column :id, Integer, :serial => true
132
- column :output, Text, :nullable => false, :default => ""
133
- column :successful, Boolean, :nullable => false, :default => false
134
- column :commit_identifier, String, :nullable => false
135
- column :commit_metadata, Yaml, :nullable => false
136
- column :created_at, DateTime
137
- column :updated_at, DateTime
138
- column :project_id, Integer
139
- end
140
-
141
- all_builds.each do |build|
142
- Build.create(:project_id => build.commit.project_id,
143
- :output => build.output,
144
- :successful => build.successful,
145
- :commit_identifier => build.commit.identifier,
146
- :commit_metadata => {
147
- :message => build.commit.message,
148
- :author => build.commit.author.full,
149
- :date => commit.committed_at
150
- }.to_yaml)
151
- end
152
-
153
- drop_table :commits
154
- end
155
138
  end
156
139
  end
157
140
  end
@@ -1,38 +1,40 @@
1
+ require File.dirname(__FILE__) + "/notifier/base"
2
+
1
3
  module Integrity
2
4
  class Notifier
3
5
  include DataMapper::Resource
4
-
5
- property :id, Serial
6
+
7
+ property :id, Integer, :serial => true
6
8
  property :name, String, :nullable => false
7
9
  property :config, Yaml, :nullable => false, :lazy => false
8
-
10
+
9
11
  belongs_to :project, :class_name => "Integrity::Project"
10
-
12
+
11
13
  validates_is_unique :name, :scope => :project_id
12
14
  validates_present :project_id
13
-
15
+
14
16
  def self.available
15
- @available ||= constants.map { |name| const_get(name) }.select { |notifier| valid_notifier?(notifier) }
17
+ constants.map { |name| const_get(name) }.select { |notifier| valid_notifier?(notifier) }
16
18
  end
17
-
19
+
18
20
  def self.enable_notifiers(project, enabled, config={})
19
21
  all(:project_id => project).destroy!
20
22
  list_of_enabled_notifiers(enabled).each do |name|
21
23
  create! :project_id => project, :name => name, :config => config[name]
22
24
  end
23
-
25
+
24
26
  end
25
-
27
+
26
28
  def notify_of_build(build)
27
29
  to_const.notify_of_build(build, config)
28
30
  end
29
-
31
+
30
32
  private
31
-
33
+
32
34
  def to_const
33
35
  self.class.module_eval(name)
34
36
  end
35
-
37
+
36
38
  def self.list_of_enabled_notifiers(names)
37
39
  [*names].reject { |n| n.nil? }
38
40
  end
@@ -44,7 +46,3 @@ module Integrity
44
46
  private_class_method :valid_notifier?
45
47
  end
46
48
  end
47
-
48
- require File.dirname(__FILE__) / "notifier" / "base"
49
-
50
- Dir["#{File.dirname(__FILE__)}/notifier/*.rb"].each &method(:require)
@@ -8,47 +8,57 @@ module Integrity
8
8
  def self.to_haml
9
9
  raise NoMethodError, "you need to implement this method in your notifier"
10
10
  end
11
-
12
- attr_reader :build
13
-
14
- def initialize(build, config)
15
- @build = build
11
+
12
+ attr_reader :commit
13
+
14
+ def initialize(commit, config)
15
+ @commit = commit
16
16
  @config = config
17
17
  end
18
-
18
+
19
+ def build
20
+ warn "Notifier::Base#build is deprecated, use Notifier::Base#commit instead"
21
+ commit
22
+ end
23
+
19
24
  def deliver!
20
25
  raise NoMethodError, "you need to implement this method in your notifier"
21
26
  end
22
-
27
+
23
28
  def short_message
24
- "Build #{build.short_commit_identifier} #{build.successful? ? "was successful" : "failed"}"
29
+ commit.human_readable_status
25
30
  end
26
-
31
+
27
32
  def full_message
28
33
  <<-EOM
29
- "Build #{build.commit_identifier} #{build.successful? ? "was successful" : "failed"}"
34
+ "Build #{commit.identifier} #{commit.successful? ? "was successful" : "failed"}"
30
35
 
31
- Commit Message: #{build.commit_message}
32
- Commit Date: #{build.commited_at}
33
- Commit Author: #{build.commit_author.name}
36
+ Commit Message: #{commit.message}
37
+ Commit Date: #{commit.committed_at}
38
+ Commit Author: #{commit.author.name}
34
39
 
35
- Link: #{build_url}
40
+ Link: #{commit_url}
36
41
 
37
42
  Build Output:
38
43
 
39
- #{stripped_build_output}
44
+ #{stripped_commit_output}
40
45
  EOM
41
46
  end
42
-
43
- def build_url
47
+
48
+ def commit_url
44
49
  raise if Integrity.config[:base_uri].nil?
45
- Integrity.config[:base_uri] / build.project.permalink / "builds" / build.commit_identifier
50
+ Integrity.config[:base_uri] / commit.project.permalink / "commits" / commit.identifier
46
51
  end
47
52
 
48
53
  private
49
54
 
55
+ def stripped_commit_output
56
+ commit.output.gsub("\e[0m", "").gsub(/\e\[3[1-7]m/, "")
57
+ end
58
+
50
59
  def stripped_build_output
51
- build.output.gsub("\e[0m", "").gsub(/\e\[3[1-7]m/, "")
60
+ warn "Notifier::Base#stripped_build_output is deprecated, use Notifier::base#stripped_commit_output instead"
61
+ stripped_commit_output
52
62
  end
53
63
  end
54
64
  end
@@ -0,0 +1,100 @@
1
+ require "hpricot"
2
+ require "haml"
3
+ require File.dirname(__FILE__) + "/../../integrity"
4
+
5
+ module Integrity
6
+ class Notifier
7
+ module TestHelpers
8
+ module HpricotAssertions
9
+ # Thanks Harry! http://gist.github.com/39960
10
+
11
+ class HpricotMatcher
12
+ def initialize(html)
13
+ @doc = Hpricot(html)
14
+ end
15
+
16
+ # elements('h1') returns a Hpricot::Elements object with all h1-tags.
17
+ def elements(selector)
18
+ @doc.search(selector)
19
+ end
20
+
21
+ # element('h1') returns Hpricot::Elem with first h1-tag, or nil if
22
+ # none exist.
23
+ def element(selector)
24
+ @doc.at(selector)
25
+ end
26
+
27
+ # tags('h1') returns the inner HTML of all matched elements mathed.
28
+ def tags(selector)
29
+ e = elements(selector)
30
+ e.map {|x| x.inner_html}
31
+ end
32
+
33
+ # tag('h1') returns the inner HTML of the first mached element, or
34
+ # nil if none matched.
35
+ def tag(selector)
36
+ e = element(selector)
37
+ e && e.inner_html
38
+ end
39
+ end
40
+
41
+ def assert_have_tag(html, selector, content=nil)
42
+ matcher = HpricotMatcher.new(html)
43
+ assert_equal content, matcher.tag(selector) if content
44
+ assert matcher.tag(selector)
45
+ end
46
+ end
47
+
48
+ module NotifierFormHelpers
49
+ include HpricotAssertions
50
+
51
+ def form(config={})
52
+ Haml::Engine.new(notifier_class.to_haml).
53
+ render(OpenStruct.new(:config => config))
54
+ end
55
+
56
+ def assert_form_have_tag(selector, options={})
57
+ content = options.delete(:content)
58
+ assert_have_tag(form(options), selector, content)
59
+ end
60
+
61
+ def assert_form_have_option(option, value=nil)
62
+ selector = "input##{notifier.downcase}_notifier_#{option}"
63
+ selector << "[@name='notifiers[#{notifier}][#{option}]']"
64
+ selector << "[@value='#{value}']" if value
65
+
66
+ assert_form_have_tag(selector, option => value)
67
+ end
68
+
69
+ def assert_form_have_options(*options)
70
+ options.each { |option| assert_form_have_option(option) }
71
+ end
72
+ end
73
+
74
+ include NotifierFormHelpers
75
+
76
+ def build
77
+ @build ||= Integrity::Build.gen(:successful)
78
+ end
79
+
80
+ def commit
81
+ @commit ||= build.commit
82
+ end
83
+
84
+ def notifier_class
85
+ Integrity::Notifier.const_get(notifier)
86
+ end
87
+
88
+ def notification
89
+ notifier_class.new(commit).body
90
+ end
91
+
92
+ def setup_database
93
+ DataMapper.setup(:default, "sqlite3::memory:")
94
+ DataMapper.auto_migrate!
95
+
96
+ require File.dirname(__FILE__) + "/../../../test/helpers/fixtures"
97
+ end
98
+ end
99
+ end
100
+ end