xmlconv2 2.0.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.
- data/History.txt +6 -0
- data/LICENCE.txt +339 -0
- data/Manifest.txt +107 -0
- data/README.txt +25 -0
- data/Rakefile +28 -0
- data/bin/admin +65 -0
- data/bin/xmlconvd +19 -0
- data/data/grammar/i2.grammar +73 -0
- data/src/conversion/bdd_geh.rb +281 -0
- data/src/conversion/bdd_i2.rb +102 -0
- data/src/conversion/bdd_xml.rb +242 -0
- data/src/conversion/geh_bdd.rb +165 -0
- data/src/conversion/i2_bdd.rb +271 -0
- data/src/conversion/xml_bdd.rb +125 -0
- data/src/custom/lookandfeel.rb +47 -0
- data/src/i2/address.rb +33 -0
- data/src/i2/date.rb +37 -0
- data/src/i2/document.rb +26 -0
- data/src/i2/header.rb +24 -0
- data/src/i2/order.rb +44 -0
- data/src/i2/parser.rb +25 -0
- data/src/i2/position.rb +37 -0
- data/src/i2/record.rb +11 -0
- data/src/model/address.rb +20 -0
- data/src/model/agreement.rb +10 -0
- data/src/model/bdd.rb +26 -0
- data/src/model/bsr.rb +16 -0
- data/src/model/delivery.rb +15 -0
- data/src/model/delivery_item.rb +18 -0
- data/src/model/freetext_container.rb +26 -0
- data/src/model/id_container.rb +15 -0
- data/src/model/invoice.rb +18 -0
- data/src/model/invoice_item.rb +11 -0
- data/src/model/item.rb +19 -0
- data/src/model/item_container.rb +15 -0
- data/src/model/name.rb +27 -0
- data/src/model/part_info.rb +10 -0
- data/src/model/part_info_container.rb +15 -0
- data/src/model/party.rb +23 -0
- data/src/model/party_container.rb +20 -0
- data/src/model/price.rb +10 -0
- data/src/model/price_container.rb +18 -0
- data/src/model/transaction.rb +28 -0
- data/src/state/global.rb +28 -0
- data/src/state/global_predefine.rb +11 -0
- data/src/state/login.rb +38 -0
- data/src/state/transaction.rb +13 -0
- data/src/state/transactions.rb +130 -0
- data/src/util/application.rb +143 -0
- data/src/util/destination.rb +130 -0
- data/src/util/invoicer.rb +72 -0
- data/src/util/known_user.rb +16 -0
- data/src/util/polling_manager.rb +78 -0
- data/src/util/session.rb +23 -0
- data/src/util/transaction.rb +105 -0
- data/src/util/validator.rb +20 -0
- data/src/view/foot.rb +27 -0
- data/src/view/head.rb +13 -0
- data/src/view/login.rb +36 -0
- data/src/view/navigation.rb +30 -0
- data/src/view/navigationlink.rb +21 -0
- data/src/view/pager.rb +73 -0
- data/src/view/preformatted.rb +39 -0
- data/src/view/template.rb +17 -0
- data/src/view/transaction.rb +42 -0
- data/src/view/transactions.rb +90 -0
- data/test/mock.rb +149 -0
- data/test/suite.rb +16 -0
- data/test/test_conversion/bdd_geh.rb +533 -0
- data/test/test_conversion/bdd_i2.rb +314 -0
- data/test/test_conversion/bdd_xml.rb +556 -0
- data/test/test_conversion/geh_bdd.rb +372 -0
- data/test/test_conversion/i2_bdd.rb +804 -0
- data/test/test_conversion/xml_bdd.rb +203 -0
- data/test/test_i2/address.rb +88 -0
- data/test/test_i2/date.rb +49 -0
- data/test/test_i2/document.rb +62 -0
- data/test/test_i2/header.rb +39 -0
- data/test/test_i2/order.rb +94 -0
- data/test/test_i2/parser.rb +309 -0
- data/test/test_i2/position.rb +65 -0
- data/test/test_integration/geh_i2.rb +312 -0
- data/test/test_integration/i2_geh.rb +449 -0
- data/test/test_integration/i2_xml.rb +162 -0
- data/test/test_integration/xml_i2.rb +254 -0
- data/test/test_model/address.rb +35 -0
- data/test/test_model/agreement.rb +22 -0
- data/test/test_model/bdd.rb +55 -0
- data/test/test_model/bsr.rb +38 -0
- data/test/test_model/delivery.rb +79 -0
- data/test/test_model/delivery_item.rb +52 -0
- data/test/test_model/freetext_container.rb +45 -0
- data/test/test_model/invoice.rb +65 -0
- data/test/test_model/invoice_item.rb +41 -0
- data/test/test_model/name.rb +57 -0
- data/test/test_model/part_info.rb +24 -0
- data/test/test_model/party.rb +96 -0
- data/test/test_model/price.rb +24 -0
- data/test/test_util/application.rb +168 -0
- data/test/test_util/destination.rb +197 -0
- data/test/test_util/invoicer.rb +36 -0
- data/test/test_util/polling_manager.rb +111 -0
- data/test/test_util/transaction.rb +129 -0
- data/user-stories/UserStories_XmlConv2.txt +381 -0
- data/user-stories/doku/ABB.incoming.HTTP.invoice.xml +315 -0
- data/user-stories/doku/ABB.received.xml +259 -0
- data/user-stories/doku/BO_OrderProcessing.sxw +0 -0
- metadata +195 -0
data/src/view/foot.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# View::Foot -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
|
3
|
+
|
|
4
|
+
require 'htmlgrid/composite'
|
|
5
|
+
require 'view/navigation'
|
|
6
|
+
require 'date'
|
|
7
|
+
|
|
8
|
+
module XmlConv
|
|
9
|
+
module View
|
|
10
|
+
class Foot < HtmlGrid::Composite
|
|
11
|
+
COMPONENTS = {
|
|
12
|
+
[0,0] => :copyright,
|
|
13
|
+
[1,0] => Navigation,
|
|
14
|
+
}
|
|
15
|
+
CSS_CLASS = 'foot composite'
|
|
16
|
+
LEGACY_INTERFACE = false
|
|
17
|
+
def copyright(model)
|
|
18
|
+
link = HtmlGrid::Link.new(:copyright, model, @session, self)
|
|
19
|
+
link.href = 'http://www.ywesee.com'
|
|
20
|
+
link.value = @lookandfeel.lookup(:copyright, Date.today.strftime('%Y'))
|
|
21
|
+
link.css_class = 'foot'
|
|
22
|
+
link.target = '_blank'
|
|
23
|
+
link
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/src/view/head.rb
ADDED
data/src/view/login.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# View::Login -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
|
3
|
+
|
|
4
|
+
require 'view/template'
|
|
5
|
+
require 'htmlgrid/form'
|
|
6
|
+
require 'htmlgrid/pass'
|
|
7
|
+
|
|
8
|
+
module XmlConv
|
|
9
|
+
module View
|
|
10
|
+
class LoginForm < HtmlGrid::Form
|
|
11
|
+
COMPONENTS = {
|
|
12
|
+
[0,0] => :pass,
|
|
13
|
+
[1,1] => :submit,
|
|
14
|
+
}
|
|
15
|
+
CSS_CLASS = 'component'
|
|
16
|
+
EVENT = :login
|
|
17
|
+
LABELS = true
|
|
18
|
+
SYMBOL_MAP = {
|
|
19
|
+
:pass => HtmlGrid::Pass,
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
class LoginComposite < HtmlGrid::Composite
|
|
23
|
+
COMPONENTS = {
|
|
24
|
+
[0,0] => "login_welcome",
|
|
25
|
+
[0,1] => LoginForm,
|
|
26
|
+
}
|
|
27
|
+
CSS_CLASS = 'composite'
|
|
28
|
+
CSS_MAP = {
|
|
29
|
+
[0,0] => 'th',
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
class Login < Template
|
|
33
|
+
CONTENT = LoginComposite
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# View::Navigation -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
|
3
|
+
|
|
4
|
+
require 'htmlgrid/composite'
|
|
5
|
+
require 'view/navigationlink'
|
|
6
|
+
|
|
7
|
+
module XmlConv
|
|
8
|
+
module View
|
|
9
|
+
class Navigation < HtmlGrid::Composite
|
|
10
|
+
COMPONENTS = {}
|
|
11
|
+
CSS_CLASS = "navigation"
|
|
12
|
+
#HTML_ATTRIBUTES = {"align"=>"right"}
|
|
13
|
+
SYMBOL_MAP = {
|
|
14
|
+
:navigation_divider => HtmlGrid::Text,
|
|
15
|
+
}
|
|
16
|
+
def init
|
|
17
|
+
build_navigation()
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
def build_navigation
|
|
21
|
+
@lookandfeel.navigation.each_with_index { |state, idx|
|
|
22
|
+
evt = state.is_a?(Symbol) ? state : state.direct_event
|
|
23
|
+
symbol_map.store(evt, NavigationLink)
|
|
24
|
+
components.store([idx*2,0], evt)
|
|
25
|
+
components.store([idx*2-1,0], :navigation_divider) if(idx > 0)
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# View::NavigationLink -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
|
3
|
+
|
|
4
|
+
require 'htmlgrid/link'
|
|
5
|
+
|
|
6
|
+
module XmlConv
|
|
7
|
+
module View
|
|
8
|
+
class NavigationLink < HtmlGrid::Link
|
|
9
|
+
#CSS_CLASS = "navigation"
|
|
10
|
+
def init
|
|
11
|
+
super
|
|
12
|
+
unless(@lookandfeel.direct_event == @name)
|
|
13
|
+
@attributes.store("href", @lookandfeel.event_url(@name))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
data/src/view/pager.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# View::Pager -- xmlconv2 -- 01.07.2004 -- ywesee@ywesee.com
|
|
3
|
+
|
|
4
|
+
require 'htmlgrid/link'
|
|
5
|
+
require 'htmlgrid/list'
|
|
6
|
+
|
|
7
|
+
module XmlConv
|
|
8
|
+
module View
|
|
9
|
+
class Pager < HtmlGrid::List
|
|
10
|
+
BACKGROUND_SUFFIX = ''
|
|
11
|
+
COMPONENTS = {
|
|
12
|
+
[0,0] => :number_link,
|
|
13
|
+
}
|
|
14
|
+
CSS_CLASS = 'pager'
|
|
15
|
+
CSS_HEAD_MAP = {
|
|
16
|
+
#[0,0] => 'pager-head',
|
|
17
|
+
}
|
|
18
|
+
CSS_MAP = {
|
|
19
|
+
#[0,0] => 'pager',
|
|
20
|
+
}
|
|
21
|
+
LEGACY_INTERFACE = false
|
|
22
|
+
OFFSET_STEP = [1,0]
|
|
23
|
+
SORT_DEFAULT = :to_i
|
|
24
|
+
SORT_HEADER = false
|
|
25
|
+
def init
|
|
26
|
+
@page = @container.model
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
def compose_header(offset)
|
|
30
|
+
@grid.add(page_number(@model), *offset)
|
|
31
|
+
@grid.add_style('head', *offset)
|
|
32
|
+
offset = resolve_offset(offset, self::class::OFFSET_STEP)
|
|
33
|
+
if(@page != @model.first)
|
|
34
|
+
link = page_link(@page.previous)
|
|
35
|
+
link.value = @lookandfeel.lookup(:page_back)
|
|
36
|
+
@grid.add(link, *offset)
|
|
37
|
+
end
|
|
38
|
+
#@grid.add_attribute('class', 'pager', *offset)
|
|
39
|
+
resolve_offset(offset, self::class::OFFSET_STEP)
|
|
40
|
+
end
|
|
41
|
+
def compose_footer(offset)
|
|
42
|
+
if(@page != @model.last)
|
|
43
|
+
link = page_link(@page.next)
|
|
44
|
+
link.value = @lookandfeel.lookup(:page_fwd)
|
|
45
|
+
@grid.add(link, *offset)
|
|
46
|
+
else
|
|
47
|
+
@grid.add(nil, *offset)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
private
|
|
51
|
+
def number_link(model)
|
|
52
|
+
page_link(model)
|
|
53
|
+
end
|
|
54
|
+
def page_link(page)
|
|
55
|
+
if(page != @page)
|
|
56
|
+
link = HtmlGrid::Link.new(:self, page, @session, self)
|
|
57
|
+
link.value = page.to_s
|
|
58
|
+
#link.set_attribute("class", "pager")
|
|
59
|
+
values = {
|
|
60
|
+
:page => page.to_i.to_s,
|
|
61
|
+
}
|
|
62
|
+
link.href = @lookandfeel.event_url(:self, values)
|
|
63
|
+
link
|
|
64
|
+
else
|
|
65
|
+
page.to_s
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
def page_number(model)
|
|
69
|
+
@lookandfeel.lookup(:page_number, @page, @page.total)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# View::Preformatted -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
|
3
|
+
|
|
4
|
+
require 'htmlgrid/value'
|
|
5
|
+
|
|
6
|
+
module XmlConv
|
|
7
|
+
module View
|
|
8
|
+
BREAK_WIDTH = 65
|
|
9
|
+
class Preformatted < HtmlGrid::Value
|
|
10
|
+
def init
|
|
11
|
+
super
|
|
12
|
+
if(@value)
|
|
13
|
+
raw = @value.gsub(/>\s+</, "><").gsub(/\r\n?/, "\n")
|
|
14
|
+
pretty = CGI.pretty(raw)
|
|
15
|
+
wrap = ''
|
|
16
|
+
pretty.each_line { |line|
|
|
17
|
+
if(line.length < BREAK_WIDTH)
|
|
18
|
+
wrap << line
|
|
19
|
+
else
|
|
20
|
+
parts = line.split('" "')
|
|
21
|
+
wrapline = parts.shift
|
|
22
|
+
while(part = parts.shift)
|
|
23
|
+
if((wrapline.length + part.length) >= BREAK_WIDTH)
|
|
24
|
+
wrap << wrapline
|
|
25
|
+
wrap << "\"\n"
|
|
26
|
+
wrapline = " \"" << part
|
|
27
|
+
else
|
|
28
|
+
wrapline << '" "' << part
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
wrap << wrapline
|
|
32
|
+
end
|
|
33
|
+
}
|
|
34
|
+
@value = CGI.escapeHTML(wrap)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# View::Template -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
|
3
|
+
|
|
4
|
+
require 'htmlgrid/divtemplate'
|
|
5
|
+
require 'view/foot'
|
|
6
|
+
|
|
7
|
+
module XmlConv
|
|
8
|
+
module View
|
|
9
|
+
class Template < HtmlGrid::DivTemplate
|
|
10
|
+
COMPONENTS = {
|
|
11
|
+
[0,0] => :content,
|
|
12
|
+
[0,1] => :foot,
|
|
13
|
+
}
|
|
14
|
+
FOOT = Foot
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# View::Transaction -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
|
3
|
+
|
|
4
|
+
require 'htmlgrid/value'
|
|
5
|
+
require 'view/template'
|
|
6
|
+
require 'view/preformatted'
|
|
7
|
+
|
|
8
|
+
module XmlConv
|
|
9
|
+
module View
|
|
10
|
+
class TransactionComposite < HtmlGrid::Composite
|
|
11
|
+
COMPONENTS = {
|
|
12
|
+
[0,0] => 'th_input',
|
|
13
|
+
[1,0] => 'th_output',
|
|
14
|
+
[0,1] => :input,
|
|
15
|
+
[1,1] => :output,
|
|
16
|
+
[0,2] => 'th_error',
|
|
17
|
+
[0,3] => :error_string,
|
|
18
|
+
}
|
|
19
|
+
CSS_CLASS = 'composite'
|
|
20
|
+
CSS_MAP = {
|
|
21
|
+
[0,0,2] => 'th',
|
|
22
|
+
[0,1,2] => 'helfti preformatted',
|
|
23
|
+
[0,2] => 'th',
|
|
24
|
+
[0,3] => 'list preformatted bg',
|
|
25
|
+
}
|
|
26
|
+
COLSPAN_MAP = {
|
|
27
|
+
[0,2] => 2,
|
|
28
|
+
[0,3] => 2,
|
|
29
|
+
}
|
|
30
|
+
DEFAULT_CLASS = View::Preformatted
|
|
31
|
+
LEGACY_INTERFACE = false
|
|
32
|
+
def error_string(model)
|
|
33
|
+
if(err = model.error)
|
|
34
|
+
[err.class, err.message, err.backtrace].join("\n")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
class Transaction < Template
|
|
39
|
+
CONTENT = TransactionComposite
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# View::Transactions -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
|
3
|
+
|
|
4
|
+
require 'htmlgrid/list'
|
|
5
|
+
require 'htmlgrid/value'
|
|
6
|
+
require 'view/template'
|
|
7
|
+
require 'view/pager'
|
|
8
|
+
|
|
9
|
+
module XmlConv
|
|
10
|
+
module View
|
|
11
|
+
class TransactionsList < HtmlGrid::List
|
|
12
|
+
BACKGROUND_SUFFIX = ' bg'
|
|
13
|
+
COMPONENTS = {
|
|
14
|
+
[0,0] => :transaction_id,
|
|
15
|
+
[1,0] => :origin,
|
|
16
|
+
[2,0] => :commit_time,
|
|
17
|
+
[3,0] => :uri_comparable,
|
|
18
|
+
[4,0] => :status_comparable,
|
|
19
|
+
}
|
|
20
|
+
CSS_CLASS = 'composite'
|
|
21
|
+
CSS_HEAD_MAP = {
|
|
22
|
+
[0,0] => 'right',
|
|
23
|
+
}
|
|
24
|
+
CSS_MAP = {
|
|
25
|
+
[0,0] => 'list right',
|
|
26
|
+
[1,0,4] => 'list',
|
|
27
|
+
}
|
|
28
|
+
DEFAULT_CLASS = HtmlGrid::Value
|
|
29
|
+
LEGACY_INTERFACE = false
|
|
30
|
+
SORT_DEFAULT = nil #:commit_time
|
|
31
|
+
SORT_REVERSE = false
|
|
32
|
+
def commit_time(model)
|
|
33
|
+
time_format(model.commit_time || model.start_time)
|
|
34
|
+
end
|
|
35
|
+
def origin(model)
|
|
36
|
+
uri_fmt(model.origin)
|
|
37
|
+
end
|
|
38
|
+
def uri_comparable(model)
|
|
39
|
+
uri_fmt(model.uri)
|
|
40
|
+
end
|
|
41
|
+
def uri_fmt(uri)
|
|
42
|
+
uri = uri.to_s
|
|
43
|
+
if((i1 = uri.index(/([^\/])\/[^\/]/, 1)) \
|
|
44
|
+
&& (i2 = uri.rindex(/(\/[^\/]+){3}/)) \
|
|
45
|
+
&& (i2 > i1))
|
|
46
|
+
uri[0..(i1.next)] << '...' << uri[i2..-1]
|
|
47
|
+
else
|
|
48
|
+
uri
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
def status_comparable(model)
|
|
52
|
+
model.update_status
|
|
53
|
+
status = model.status
|
|
54
|
+
@lookandfeel.lookup("status_#{status}") or status.to_s
|
|
55
|
+
end
|
|
56
|
+
def time_format(a_time)
|
|
57
|
+
if(a_time.respond_to?(:strftime))
|
|
58
|
+
a_time.strftime("%d.%m.%Y %H:%M:%S")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
def transaction_id(model)
|
|
62
|
+
link = HtmlGrid::Link.new(:transaction_id, model, @session, self)
|
|
63
|
+
args = {
|
|
64
|
+
'transaction_id' => model.transaction_id,
|
|
65
|
+
}
|
|
66
|
+
link.href = @lookandfeel.event_url(:transaction, args)
|
|
67
|
+
link.value = model.transaction_id
|
|
68
|
+
link
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
class TransactionsComposite < HtmlGrid::Composite
|
|
72
|
+
COMPONENTS = {
|
|
73
|
+
[0,0] => :pager,
|
|
74
|
+
[0,1] => :transactions,
|
|
75
|
+
}
|
|
76
|
+
CSS_CLASS = 'composite'
|
|
77
|
+
LEGACY_INTERFACE = false
|
|
78
|
+
def pager(model)
|
|
79
|
+
Pager.new(model.pages, @session, self)
|
|
80
|
+
end
|
|
81
|
+
def transactions(page)
|
|
82
|
+
TransactionsList.new(page.model, @session, self)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
class Transactions < Template
|
|
86
|
+
CONTENT = TransactionsComposite
|
|
87
|
+
#CONTENT = TransactionsList
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/test/mock.rb
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Ruby/Mock version 1.0
|
|
2
|
+
#
|
|
3
|
+
# A class for conveniently building mock objects in RUnit test cases.
|
|
4
|
+
# Copyright (c) 2001 Nat Pryce, all rights reserved
|
|
5
|
+
#
|
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
|
8
|
+
# the Free Software Foundation; either version 2 of the License.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
18
|
+
|
|
19
|
+
require 'runit/error'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Mock
|
|
23
|
+
# Creates a new, named mock object. The name is reported in exceptions
|
|
24
|
+
# thrown by the mock object when method invocations are incorrect.
|
|
25
|
+
#
|
|
26
|
+
def initialize( mock_name = self.to_s )
|
|
27
|
+
@mock_calls = []
|
|
28
|
+
@next_call = 0
|
|
29
|
+
@name = mock_name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Mock the next method call to be made to this mock object.
|
|
33
|
+
#
|
|
34
|
+
# A mock method is defined by the method name (a symbol) and a block
|
|
35
|
+
# that defines the arity of the method and the mocked behaviour for
|
|
36
|
+
# this call. The mocked behaviour should assert preconditions and
|
|
37
|
+
# return a value. Mocked behaviour should rarely be any more complex
|
|
38
|
+
# than that. If it is, that's probably an indication that the tests
|
|
39
|
+
# need some restructuring or that the tested code needs refactoring.
|
|
40
|
+
#
|
|
41
|
+
# If no block is given and preconditions have been defined for the named
|
|
42
|
+
# method, a block is created for the mocked methodthat has the same arity
|
|
43
|
+
# as the precondition and returns self.
|
|
44
|
+
#
|
|
45
|
+
def __next( name, &test )
|
|
46
|
+
if test == nil
|
|
47
|
+
if respond_to?( Mock.__pre(name) )
|
|
48
|
+
test = proc { |*args| self }
|
|
49
|
+
else
|
|
50
|
+
raise "no block given for mocked method #{name}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
@mock_calls.push( [name,test] )
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Call this at the end of a test to ensure that all scheduled calls
|
|
57
|
+
# have been made to the mock
|
|
58
|
+
#
|
|
59
|
+
def __verify
|
|
60
|
+
if @next_call != @mock_calls.length
|
|
61
|
+
raise RUNIT::AssertionFailedError,
|
|
62
|
+
"not all expected method calls were made to #{@name}",
|
|
63
|
+
caller
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
# Dispatches aribtrary method calls to the next mocked behaviour
|
|
70
|
+
#
|
|
71
|
+
def method_missing( name, *args, &block )
|
|
72
|
+
__mock_call( name, args, (block_given? ? block : nil) )
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Implements a method call using the next mocked behaviour and asserts
|
|
76
|
+
# that the expected method is called with the expected number of
|
|
77
|
+
# arguments.
|
|
78
|
+
#
|
|
79
|
+
def __mock_call( name, args, block )
|
|
80
|
+
if @next_call >= @mock_calls.length
|
|
81
|
+
raise RUNIT::AssertionFailedError,
|
|
82
|
+
"unexpected call to #{name} method of #{@name}",
|
|
83
|
+
caller(2)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
expected_name,body = @mock_calls[@next_call]
|
|
87
|
+
@next_call += 1
|
|
88
|
+
|
|
89
|
+
if name != expected_name
|
|
90
|
+
raise RUNIT::AssertionFailedError,
|
|
91
|
+
"wrong method called on #{@name}; " +
|
|
92
|
+
"expected #{expected_name}, was #{name}",
|
|
93
|
+
caller(2)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
args_length = args.length + (block ? 1 : 0)
|
|
97
|
+
|
|
98
|
+
if body.arity < 0
|
|
99
|
+
if (body.arity+1).abs > args_length
|
|
100
|
+
raise RUNIT::AssertionFailedError,
|
|
101
|
+
"too few arguments to #{name} method of #{@name}; " +
|
|
102
|
+
"require #{(body.arity+1).abs}, got #{args.length}",
|
|
103
|
+
caller(2)
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
if body.arity != args_length
|
|
107
|
+
raise RUNIT::AssertionFailedError,
|
|
108
|
+
"wrong number of arguments to " +
|
|
109
|
+
"#{name} method of #{@name}; " +
|
|
110
|
+
"require #{body.arity}, got #{args.length}",
|
|
111
|
+
caller(2)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if respond_to? Mock.__pre(name)
|
|
116
|
+
if block
|
|
117
|
+
precondition_ok = __send__( Mock.__pre(name), *args, &block )
|
|
118
|
+
else
|
|
119
|
+
precondition_ok = __send__( Mock.__pre(name), *args )
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if not precondition_ok
|
|
123
|
+
raise RUNIT::AssertionFailedError,
|
|
124
|
+
"precondition of #{name} method violated",
|
|
125
|
+
caller(2)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
if block
|
|
130
|
+
instance_eval { body.call( block, *args ) }
|
|
131
|
+
else
|
|
132
|
+
instance_eval { body.call( *args ) }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# The name of a precondition for a method
|
|
137
|
+
def Mock.__pre( method )
|
|
138
|
+
"__pre_#{method.to_i}".intern
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def Mock.method_added( name )
|
|
143
|
+
unless(/^__pre_/.match(name.to_s))
|
|
144
|
+
pre = self.__pre(name)
|
|
145
|
+
alias_method( pre, name )
|
|
146
|
+
undef_method(name)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|