lazylead 0.8.0 → 0.9.1

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.
@@ -36,7 +36,7 @@ module Lazylead
36
36
  end
37
37
 
38
38
  def run(sys, postman, opts)
39
- assignments = sys.issues(opts["jql"])
39
+ assignments = sys.issues(opts["jql"], opts.jira_defaults)
40
40
  .group_by(&:assignee)
41
41
  .map { |user, tasks| [user.id, Teammate.new(user, tasks)] }
42
42
  .to_h
@@ -71,6 +71,10 @@ module Lazylead
71
71
  def to_s
72
72
  "#{id} has #{total} tasks"
73
73
  end
74
+
75
+ def sprints
76
+ @tasks.group_by(&:sprint).sort
77
+ end
74
78
  end
75
79
 
76
80
  # The teammate without tasks.
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The MIT License
4
+ #
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"),
9
+ # to deal in the Software without restriction, including without limitation
10
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
11
+ # and/or sell copies of the Software, and to permit persons to whom
12
+ # the Software is furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included
15
+ # in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
23
+ # OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ require "date"
26
+ require_relative "../log"
27
+ require_relative "../opts"
28
+ require_relative "../system/jira"
29
+
30
+ module Lazylead
31
+ module Task
32
+ #
33
+ # Email alerts about due date modification by not-authorized persons.
34
+ #
35
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
36
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
37
+ # License:: MIT
38
+ class Micromanager
39
+ def initialize(log = Log.new)
40
+ @log = log
41
+ end
42
+
43
+ def run(sys, postman, opts)
44
+ allowed = opts.slice "allowed", ","
45
+ dues = sys.issues(opts["jql"], opts.jira_defaults.merge(expand: "changelog"))
46
+ .map { |i| Due.new(i, allowed) }
47
+ .select(&:illegal?)
48
+ return if dues.empty?
49
+ postman.send opts.merge(dues: dues)
50
+ end
51
+ end
52
+
53
+ # Instance of "Due" history item for the particular ticket.
54
+ class Due
55
+ attr_reader :issue, :when
56
+
57
+ def initialize(issue, allowed)
58
+ @issue = issue
59
+ @allowed = allowed
60
+ end
61
+
62
+ # Gives true when last change of "Due Date" field was done
63
+ # by not authorized person.
64
+ def illegal?
65
+ return false if @issue.assignee.id.eql?(last.id)
66
+ @allowed.none? { |a| a.eql? last.id }
67
+ end
68
+
69
+ # Detect details about last change of "Due Date" to non-null value
70
+ def last
71
+ @last ||= begin
72
+ dd = @issue.history
73
+ .reverse
74
+ .find { |h| h["items"].any? { |i| i["field"] == "duedate" } }
75
+ if dd.nil? && !@issue.duedate.nil?
76
+ @when = @issue["created"]
77
+ dd = @issue.reporter
78
+ else
79
+ @when = dd["created"].to_date
80
+ dd = Lazylead::User.new(dd["author"])
81
+ end
82
+ dd
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -22,6 +22,7 @@
22
22
  # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
+ require "zip"
25
26
  require "tempfile"
26
27
  require "nokogiri"
27
28
  require "backtrace"
@@ -49,6 +50,7 @@ module Lazylead
49
50
  "-r#{opts['since_rev']}:HEAD #{opts['svn_url']}"
50
51
  ]
51
52
  stdout = `#{cmd.join(" ")}`
53
+ stdout.scrub!
52
54
  send_email postman, opts.merge(stdout: stdout) unless stdout.blank?
53
55
  end
54
56
 
@@ -58,19 +60,39 @@ module Lazylead
58
60
  def send_email(postman, opts)
59
61
  Dir.mktmpdir do |dir|
60
62
  name = "svn-log-#{Date.today.strftime('%d-%b-%Y')}.html"
61
- f = File.open(File.join(dir, name), "w")
62
63
  begin
63
- f.write opts.msg_body("template-attachment")
64
- f.close
65
- postman.send opts.merge(attachments: [f.path])
64
+ postman.send opts.merge(attachments: [to_f(File.join(dir, name), opts)])
66
65
  ensure
67
- File.delete(f)
66
+ FileUtils.rm_rf("#{dir}/*")
68
67
  end
69
68
  rescue StandardError => e
70
- @log.error "ll-010: Can't send an email for #{opts} due to " \
69
+ @log.error "ll-010: Can't send an email '#{opts['subject']}' to #{opts['to']} due to " \
71
70
  "#{Backtrace.new(e)}'"
72
71
  end
73
72
  end
73
+
74
+ # Wrap attachment content to a *.zip file and archive.
75
+ # to_f('my-content.html', opts) => my-content.html.zip
76
+ #
77
+ # You may disable archiving option by passing option *no_archive*
78
+ # to_f('my-content.html', "no_archive" => true)
79
+ def to_f(path, opts)
80
+ if opts.key? "no_archive"
81
+ f = File.open(path, "w")
82
+ body = opts.msg_body("template-attachment")
83
+ else
84
+ f = File.new("#{path}.zip", "wb")
85
+ bytes = Zip::OutputStream.write_buffer do |zio|
86
+ zio.put_next_entry(File.basename(path))
87
+ zio.write opts.msg_body("template-attachment")
88
+ end
89
+ bytes.rewind # reposition buffer pointer to the beginning
90
+ body = bytes.sysread
91
+ end
92
+ f.write body
93
+ f.close
94
+ f.path
95
+ end
74
96
  end
75
97
  end
76
98
  end
@@ -23,5 +23,5 @@
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
25
  module Lazylead
26
- VERSION = "0.8.0"
26
+ VERSION = "0.9.1"
27
27
  end
@@ -0,0 +1,120 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <style>
5
+ /* CSS styles taken from https://github.com/yegor256/tacit */
6
+ th {
7
+ font-weight: 600
8
+ }
9
+
10
+ table tr {
11
+ border-bottom-width: 2.16px
12
+ }
13
+
14
+ table tr th {
15
+ border-bottom-width: 2.16px
16
+ }
17
+
18
+ table tr td, table tr th {
19
+ overflow: hidden;
20
+ padding: 5.4px 3.6px
21
+ }
22
+
23
+ a {
24
+ color: #275a90;
25
+ text-decoration: none
26
+ }
27
+
28
+ a:hover {
29
+ text-decoration: underline
30
+ }
31
+
32
+ pre, code, kbd, samp, var, output {
33
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
34
+ font-size: 13px
35
+ }
36
+
37
+ pre code {
38
+ background: none;
39
+ border: 0;
40
+ line-height: 29.7px;
41
+ padding: 0
42
+ }
43
+
44
+ code, kbd {
45
+ background: #daf1e0;
46
+ border-radius: 3.6px;
47
+ color: #2a6f3b;
48
+ display: inline-block;
49
+ line-height: 18px;
50
+ padding: 3.6px 6.3px 2.7px
51
+ }
52
+
53
+ * {
54
+ border: 0;
55
+ border-collapse: separate;
56
+ border-spacing: 0;
57
+ box-sizing: border-box;
58
+ margin: 0;
59
+ max-width: 100%;
60
+ padding: 0;
61
+ vertical-align: baseline;
62
+ font-family: system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif;
63
+ font-size: 13px;
64
+ font-stretch: normal;
65
+ font-style: normal;
66
+ font-weight: 400;
67
+ line-height: 29.7px
68
+ }
69
+
70
+ html, body {
71
+ width: 100%
72
+ }
73
+
74
+ html {
75
+ height: 100%
76
+ }
77
+
78
+ body {
79
+ background: #fff;
80
+ color: #1a1919;
81
+ padding: 36px
82
+ }
83
+ </style>
84
+ <title>Not authorized "Assignee" change</title>
85
+ </head>
86
+ <body>
87
+ <p>Hi,</p>
88
+
89
+ <p>The <span style='font-weight:bold'>'Duedate'</span> for the following
90
+ ticket(s) changed by not authorized person(s):</p>
91
+ <table summary="ticket(s) where duedate changed">
92
+ <tr>
93
+ <th id="key">Key</th>
94
+ <th id="priority">Priority</th>
95
+ <th id="when">When</th>
96
+ <th id="who">Who</th>
97
+ <th id="to">To</th>
98
+ <th id="summary">Summary</th>
99
+ <th id="reporter">Reporter</th>
100
+ </tr>
101
+ <% dues.each do |d| %>
102
+ <tr>
103
+ <td><a href='<%= d.issue.url %>'><%= d.issue.key %></a></td>
104
+ <td><%= d.issue.priority %></td>
105
+ <td><%= d.when %></td>
106
+ <td><span style='color: red'><%= d.last.name %></span> (<%= d.last.id %>)</td>
107
+ <td><span style='color: red'><%= d.issue.duedate %></span></td>
108
+ <td><%= d.issue.summary %></td>
109
+ <td><%= d.issue.reporter.name %></td>
110
+ </tr>
111
+ <% end %>
112
+ </table>
113
+
114
+ <p>Authorized person(s) are: <code><%= allowed %></code>.</p>
115
+
116
+ <p>Posted by
117
+ <a href="https://github.com/dgroup/lazylead">lazylead v<%= version %></a>.
118
+ </p>
119
+ </body>
120
+ </html>
@@ -69,10 +69,9 @@
69
69
  <body>
70
70
  <table summary="tickets">
71
71
  <tr>
72
- <th id="uid">ID</th>
73
72
  <th id="name">User</th>
74
73
  <th id="total">
75
- <a href="https://jira.spring.io/issues/?jql=<%= CGI.escape(jql) %>">Assigned From</a>
74
+ <a href="<%= search_link %><%= CGI.escape(jql) %>">Assigned From</a>
76
75
  </th>
77
76
  <th id="duedate">Next Due date</th>
78
77
  </tr>
@@ -83,17 +82,19 @@
83
82
  <tr>
84
83
  <% end %>
85
84
  <td>
86
- <div class="auto"><a href="<%= user_link %>"><%= teammate %></a></div>
87
- </td>
88
- <td>
89
- <div class="auto"><%= assignment.name %></div>
85
+ <div class="auto"><a href="<%= user_link %><%= teammate %>"><%= assignment.name %></a></div>
90
86
  </td>
91
87
  <td>
92
88
  <div class="auto">
93
89
  <% if assignment.free? %>
94
90
  <span style="color: red">0</span>
95
91
  <% else %>
96
- <a href="https://jira.spring.io/issues/?jql=<%= CGI.escape("#{jql} and assignee=#{teammate}") %>"><%= assignment.size %></a>
92
+ <% assignment.sprints.each do |s| %>
93
+ <a href="<%= search_link %><%= CGI.escape("#{jql} and assignee=#{teammate}") %>">
94
+ <%= s.first.blank? ? "No sprint" : s.first %>: <%= s.last.size %>
95
+ </a>
96
+ <br/>
97
+ <% end %>
97
98
  <% end %>
98
99
  </div>
99
100
  </td>
data/readme.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Hits-of-Code](https://hitsofcode.com/github/dgroup/lazylead)](https://hitsofcode.com/view/github/dgroup/lazylead)
8
8
  [![License: MIT](https://img.shields.io/github/license/mashape/apistatus.svg)](./license.txt)
9
9
 
10
- [![Build status circleci](https://circleci.com/gh/dgroup/lazylead.svg?style=shield)](https://circleci.com/gh/dgroup/lazylead)
10
+ [![Build status circleci](https://circleci.com/gh/dgroup/lazylead/tree/master.svg?style=shield)](https://circleci.com/gh/dgroup/lazylead/tree/master)
11
11
  [![0pdd](http://www.0pdd.com/svg?name=dgroup/lazylead)](http://www.0pdd.com/p?name=dgroup/lazylead)
12
12
  [![Dependency Status](https://requires.io/github/dgroup/lazylead/requirements.svg?branch=master)](https://requires.io/github/dgroup/lazylead/requirements/?branch=master)
13
13
  [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=dgroup_lazylead&metric=alert_status)](https://sonarcloud.io/dashboard?id=dgroup_lazylead)
@@ -30,23 +30,31 @@ I think you remember how [static code analysis](https://en.wikipedia.org/wiki/St
30
30
 
31
31
  Join our telegram group [lazylead.org](https://t.me/lazyleads) for discussions.
32
32
 
33
- | Daily annoying task | Jira | Github | Trello |
34
- | :---------------------------------------------------------------------------------- | :---: | :----: | :----: |
35
- | [Notify ticket's assignee](lib/lazylead/task/alert/alert.rb) | ✅ | ⌛ | ⌛ |
36
- | [Notify ticket's reporter](lib/lazylead/task/alert/alert.rb) | ✅ | ⌛ | ⌛ |
37
- | [Notify ticket's manager](lib/lazylead/task/alert/alert.rb) | ✅ | ⌛ | ⌛ |
38
- | [Notify about illegal "Fix Version" modification](lib/lazylead/task/fix_version.rb) | ✅ | ❌ | ❌ |
39
- | [Expected comment in ticket is missing](lib/lazylead/task/missing_comment.rb) | ✅ | ⌛ | ⌛ |
40
- | [Propagate some fields from parent ticket into sub-tasks](.docs/propagate_down.md) | ✅ | ❌ | ❌ |
41
- | [Evaluate the ticket formatting accuracy](.docs/accuracy.md) | ✅ | ⌛ | ⌛ |
42
- | Print the current capacity of team into newly created tasks | ⌛ | ⌛ | ⌛ |
43
- | Create/retrofit the defect automatically into latest release | ⌛ | ⌛ | ❌ |
44
- | [Notify about expired(ing) due dates](.docs/duedate_expired.md) | ✅ | ❌ | ⌛ |
45
- | Notify about absent original estimations | ⌛ | ⌛ | ⌛ |
46
- | Notify about 'Hot potato' tickets | ⌛ | ⌛ | ⌛ |
47
- | Notify about long live tickets (aging) | ⌛ | ⌛ | ⌛ |
48
- | Notify about tickets with invalid format (missing url/stacktrace, etc) | ⌛ | ⌛ | ⌛ |
49
- | Create a meeting(s) automatically in case some tickets appeared (group by assignee/reporters/component/ticket type/etc) | ⌛ | ⌛ | |
33
+ | Daily annoying task | Jira | Github | Trello | SVN |
34
+ | :---------------------------------------------------------------------------------- | :---: | :----: | :----: | :----: |
35
+ | [Notify ticket's assignee](lib/lazylead/task/alert/alert.rb) | ✅ | ⌛ | ⌛ | ❌ |
36
+ | [Notify ticket's reporter](lib/lazylead/task/alert/alert.rb) | ✅ | ⌛ | ⌛ | ❌ |
37
+ | [Notify ticket's manager](lib/lazylead/task/alert/alert.rb) | ✅ | ⌛ | ⌛ | ❌ |
38
+ | [Notify about illegal "Fix Version" modification](lib/lazylead/task/fix_version.rb) | ✅ | ❌ | ❌ | ❌ |
39
+ | [Expected comment in ticket is missing](lib/lazylead/task/missing_comment.rb) | ✅ | ⌛ | ⌛ | ❌ |
40
+ | [Propagate some fields from parent ticket into sub-tasks](.docs/propagate_down.md) | ✅ | ❌ | ❌ | ❌ |
41
+ | [Evaluate the ticket formatting accuracy](.docs/accuracy.md) | ✅ | ⌛ | ⌛ | ❌ |
42
+ | Print the current capacity of team into newly created tasks | ⌛ | ⌛ | ⌛ | ❌ |
43
+ | Create/retrofit the defect automatically into latest release | ⌛ | ⌛ | ❌ | ❌ |
44
+ | [Notify about expired(ing) due dates](.docs/duedate_expired.md) | ✅ | ❌ | ⌛ | ❌ |
45
+ | Notify about absent original estimations | ⌛ | ⌛ | ⌛ | ❌ |
46
+ | Notify about 'Hot potato' tickets | ⌛ | ⌛ | ⌛ | ❌ |
47
+ | Notify about long live tickets (aging) | ⌛ | ⌛ | ⌛ | ❌ |
48
+ | Create a meeting(s) automatically in case some tickets appeared (group by assignee/reporters/component/ticket type/etc) | ⌛ | ⌛ | ⌛ | ❌ |
49
+ | Propogate fields from parent tickets to sub-tasks | | ⌛ | ⌛ | |
50
+ | Notify about tickets without comments with expected text | ✅ | ⌛ | ⌛ | ❌ |
51
+ | Notify about team loading (no tasks on teammates) | ✅ | ⌛ | ⌛ | ❌ |
52
+ | Notify about tickets matches predefined multiple conditions | ✅ | ⌛ | ⌛ | ❌ |
53
+ | Link automatically the ticket and Confluence page if link found in ticket's comments/description | ✅ | ⌛ | ⌛ | ❌ |
54
+ | Notify about tickets assigned to your team members not by effective managers| ✅ | ⌛ | ⌛ | ❌ |
55
+ | Notify about modifications of important files in VCS | ❌ | ⌛ | ❌ | ✅ |
56
+ | Notify about diff changes for past X period in VCS | ❌ | ⌛ | ❌ | ✅ |
57
+ | Notify about changes with some text for past X period in VCS | ❌ | ⌛ | ❌ | ✅ |
50
58
 
51
59
  | Integration | Type | Status |
52
60
  | :---------------------------------------------------- | :-----------: | :----: |
@@ -23,7 +23,9 @@
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
25
  require_relative "../../../test"
26
+ require_relative "../../../../lib/lazylead/system/jira"
26
27
  require_relative "../../../../lib/lazylead/task/accuracy/accuracy"
28
+ require_relative "../../../../lib/lazylead/task/accuracy/affected_build"
27
29
 
28
30
  module Lazylead
29
31
  class ScoreTest < Lazylead::Test
@@ -42,5 +44,41 @@ module Lazylead
42
44
  Lazylead::Score.new({}, {}).grade(k.to_s.to_f)
43
45
  end
44
46
  end
47
+
48
+ test "comment has proper structure" do
49
+ assert_words %w[triage accuracy is 100.0%],
50
+ Score.new(
51
+ Struct.new(:key) do
52
+ def reporter
53
+ OpenStruct.new(id: "userid")
54
+ end
55
+
56
+ def fields
57
+ { "versions" => ["0.1.0"] }
58
+ end
59
+ end.new("DATAJDBC-493"),
60
+ Opts.new(
61
+ rules: [Lazylead::AffectedBuild.new],
62
+ total: 0.5,
63
+ "colors" => {
64
+ "0" => "#FF4F33",
65
+ "35" => "#FF9F33",
66
+ "57" => "#19DD1E",
67
+ "90" => "#0FA81A"
68
+ }.to_json.to_s,
69
+ "docs" => "https://github.com/dgroup/lazylead/blob/master/.github/ISSUE_TEMPLATE/bug_report.md"
70
+ )
71
+ ).evaluate.comment
72
+ end
73
+
74
+ test "detect non-system reporter" do
75
+ assert_equal "grussell",
76
+ Score.new(
77
+ NoAuthJira.new("https://jira.spring.io")
78
+ .issues("key=INT-4116", expand: "changelog")
79
+ .first,
80
+ Opts.new("system-users" => "abilan")
81
+ ).reporter
82
+ end
45
83
  end
46
84
  end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The MIT License
4
+ #
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"),
9
+ # to deal in the Software without restriction, including without limitation
10
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
11
+ # and/or sell copies of the Software, and to permit persons to whom
12
+ # the Software is furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included
15
+ # in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
23
+ # OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ require_relative "../../../test"
26
+ require_relative "../../../../lib/lazylead/task/accuracy/screenshots"
27
+ require_relative "../../../../lib/lazylead/system/jira"
28
+
29
+ module Lazylead
30
+ class ScreenshotsTest < Lazylead::Test
31
+ test "issue has two .png files with reference in description" do
32
+ assert Screenshots.new.passed(
33
+ OpenStruct.new(
34
+ description: "Hi,\n here are snapshots !img1.jpg|thumbnail!\n!img2.jpg|thumbnail!\n",
35
+ fields: {
36
+ "description" => "Hi,\n here are snapshots !img1.jpg|thumbnail!\n!img2.jpg|thumbnail!\n"
37
+ },
38
+ attachments: [
39
+ OpenStruct.new("filename" => "img1.jpg"),
40
+ OpenStruct.new("filename" => "img2.jpg")
41
+ ]
42
+ )
43
+ )
44
+ end
45
+
46
+ test "issue has several .png attachments mentioned using !xxx|thumbnail! option" do
47
+ assert Screenshots.new.passed(
48
+ NoAuthJira.new("https://jira.spring.io")
49
+ .issues("key=SPR-15729", fields: %w[attachment description])
50
+ .first
51
+ )
52
+ end
53
+
54
+ test "issue has no .png file however minimum 1 are required" do
55
+ refute Screenshots.new.passed(
56
+ OpenStruct.new(
57
+ description: "Hi,\n here are snapshots !img1.zip!\n",
58
+ fields: { "description" => "Hi,\n here are snapshots !img1.zip!\n" },
59
+ attachments: [
60
+ OpenStruct.new("filename" => "img1.jpg"),
61
+ OpenStruct.new("filename" => "img2.jpg")
62
+ ]
63
+ )
64
+ )
65
+ end
66
+
67
+ test "issue has two .png files with reference in description but with extension mismatch" do
68
+ refute Screenshots.new.passed(
69
+ OpenStruct.new(
70
+ description: "Hi,\n here are snapshots !img1.jpg|thumbnail!\n!img2.jpg|thumbnail!\n",
71
+ fields: {
72
+ "description" => "Hi,\n here are snapshots !img1.jpg|thumbnail!\n!img2.jpg|thumbnail!\n"
73
+ },
74
+ attachments: [
75
+ OpenStruct.new("filename" => "img1.JPG"),
76
+ OpenStruct.new("filename" => "img2.jpg")
77
+ ]
78
+ )
79
+ )
80
+ end
81
+
82
+ test "issue has two .png file in description but three .png in attachments" do
83
+ assert Screenshots.new.passed(
84
+ OpenStruct.new(
85
+ description: "Hi,\n here are snapshots !img1.JPG|thumbnail!\n!img2.jpg|thumbnail!\n",
86
+ fields: {
87
+ "description" => "Hi,\n here are snapshots !img1.JPG|thumbnail!\n!img2.jpg|thumbnail!\n"
88
+ },
89
+ attachments: [
90
+ OpenStruct.new("filename" => "img1.JPG"),
91
+ OpenStruct.new("filename" => "img2.jpg"),
92
+ OpenStruct.new("filename" => "img3.jpg")
93
+ ]
94
+ )
95
+ )
96
+ end
97
+
98
+ test "issue has two .png files with reference in description without thumbnail" do
99
+ assert Screenshots.new.passed(
100
+ OpenStruct.new(
101
+ description: "Hi,\n here are snapshots !img1.jpg!\n!img2.jpg!\n",
102
+ fields: { "description" => "Hi,\n here are snapshots !img1.jpg!\n!img2.jpg!\n" },
103
+ attachments: [
104
+ OpenStruct.new("filename" => "img1.jpg"),
105
+ OpenStruct.new("filename" => "img2.jpg")
106
+ ]
107
+ )
108
+ )
109
+ end
110
+
111
+ test "issue has two .png files with reference in description but absent in attachments" do
112
+ refute Screenshots.new.passed(
113
+ OpenStruct.new(
114
+ description: "Hi,\n here are snapshots !img1.jpg!\n!img2.jpg!\n",
115
+ fields: {
116
+ "description" => "Hi,\n here are snapshots !img1.jpg!\n!img2.jpg!\n"
117
+ },
118
+ attachments: [
119
+ OpenStruct.new("filename" => "img3.jpg"),
120
+ OpenStruct.new("filename" => "img4.jpg")
121
+ ]
122
+ )
123
+ )
124
+ end
125
+
126
+ test "two screenshots with docx attach" do
127
+ assert Screenshots.new.passed(
128
+ OpenStruct.new(
129
+ description: "Hi,\n here are snapshots !img1.jpg!\n!img2.jpg!\n[example.docx^!https://jira.com/images/icons/link_attachment_5.gif|width=7,height=7! ^|https://jira.com/secure/attachment/23/23_example.docx]",
130
+ fields: { "description" => "-" },
131
+ attachments: [
132
+ OpenStruct.new("filename" => "img1.jpg"),
133
+ OpenStruct.new("filename" => "img2.jpg"),
134
+ OpenStruct.new("filename" => "example.docx")
135
+ ]
136
+ )
137
+ )
138
+ end
139
+ end
140
+ end