ydim 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env ruby
2
+ # RootSession -- ydim -- 10.01.2006 -- hwyss@ywesee.com
3
+
4
+ require 'drb'
5
+ require 'encoding/character/utf-8'
6
+ require 'ydim/autoinvoicer'
7
+ require 'ydim/debitor'
8
+ require 'ydim/invoice'
9
+ require 'ydim/item'
10
+ require 'ydim/mail'
11
+ require 'odba'
12
+
13
+ module YDIM
14
+ class RootSession
15
+ attr_accessor :serv, :client
16
+ def initialize(user)
17
+ @user = user
18
+ end
19
+ def add_items(invoice_id, items, invoice_key=:invoice)
20
+ @serv.logger.debug(whoami) {
21
+ size = (items.respond_to?(:size)) ? items.size : nil
22
+ "add_items(#{invoice_id}, #{items.class}[#{size}], #{invoice_key})" }
23
+ invoice = self.send(invoice_key, invoice_id)
24
+ rate = invoice.suppress_vat ? 0 : @serv.config.vat_rate
25
+ items.each { |data|
26
+ item = Item.new({:vat_rate => rate}.update(data))
27
+ invoice.add_item(item)
28
+ }
29
+ invoice.odba_store
30
+ invoice.items
31
+ end
32
+ def autoinvoice(invoice_id)
33
+ @serv.logger.debug(whoami) { "autoinvoice #{invoice_id}" }
34
+ AutoInvoice.find_by_unique_id(invoice_id.to_s) \
35
+ or begin
36
+ msg = "invalid invoice_id: #{invoice_id}"
37
+ @serv.logger.error(whoami) { msg }
38
+ raise IndexError, msg
39
+ end
40
+ end
41
+ def collect_garbage(debitor_id=nil)
42
+ @serv.logger.info(whoami) { "collect_garbage" }
43
+ deleted = []
44
+ Invoice.odba_extent { |inv|
45
+ if([nil, inv.debitor_id].include?(debitor_id) && inv.deleted)
46
+ deleted.push(inv.info)
47
+ inv.odba_delete
48
+ end
49
+ }
50
+ deleted unless(deleted.empty?)
51
+ end
52
+ def create_autoinvoice(debitor_id)
53
+ @serv.logger.debug(whoami) { "create_autoinvoice(#{debitor_id})" }
54
+ ODBA.transaction {
55
+ @serv.factory.create_autoinvoice(debitor(debitor_id))
56
+ }
57
+ end
58
+ def create_debitor
59
+ @serv.logger.info(whoami) { "create_debitor" }
60
+ ODBA.transaction {
61
+ id = @serv.id_server.next_id(:debitor)
62
+ Debitor.new(id).odba_store
63
+ }
64
+ end
65
+ def create_invoice(debitor_id)
66
+ @serv.logger.debug(whoami) { "create_invoice(#{debitor_id})" }
67
+ ODBA.transaction {
68
+ @serv.factory.create_invoice(debitor(debitor_id))
69
+ }
70
+ end
71
+ def currency_converter
72
+ @serv.logger.debug(whoami) { "currency_converter" }
73
+ @serv.currency_converter.drb_dup
74
+ end
75
+ def debitor(debitor_id)
76
+ @serv.logger.debug(whoami) { "debitor #{debitor_id}" }
77
+ Debitor.find_by_unique_id(debitor_id.to_s)\
78
+ or begin
79
+ msg = "invalid debitor_id: #{debitor_id}"
80
+ @serv.logger.error(whoami) { msg }
81
+ raise IndexError, msg
82
+ end
83
+ end
84
+ def debitors
85
+ @serv.logger.debug(whoami) { "debitors" }
86
+ Debitor.odba_extent
87
+ end
88
+ def delete_autoinvoice(invoice_id)
89
+ @serv.logger.debug(whoami) {
90
+ "delete_autoinvoice(#{invoice_id})" }
91
+ if(invoice = autoinvoice(invoice_id))
92
+ invoice.odba_delete
93
+ end
94
+ end
95
+ def delete_item(invoice_id, index, invoice_key=:invoice)
96
+ @serv.logger.debug(whoami) {
97
+ "delete_item(#{invoice_id}, #{index}, #{invoice_key})" }
98
+ invoice = self.send(invoice_key, invoice_id)
99
+ invoice.items.delete_if { |item| item.index == index }
100
+ invoice.odba_store
101
+ invoice.items
102
+ end
103
+ def generate_invoice(invoice_id)
104
+ @serv.logger.info(whoami) { "generate_invoice(#{invoice_id})" }
105
+ invoice = autoinvoice(invoice_id)
106
+ AutoInvoicer.new(@serv).generate(invoice)
107
+ end
108
+ def invoice(invoice_id)
109
+ @serv.logger.debug(whoami) { "invoice #{invoice_id}" }
110
+ Invoice.find_by_unique_id(invoice_id.to_s) \
111
+ or begin
112
+ msg = "invalid invoice_id: #{invoice_id}"
113
+ @serv.logger.error(whoami) { msg }
114
+ raise IndexError, msg
115
+ end
116
+ end
117
+ def invoice_infos(status=nil)
118
+ @serv.logger.debug(whoami) { "invoice_infos(#{status})" }
119
+ Invoice.search_by_status(status).collect { |inv| inv.info }
120
+ end
121
+ def search_debitors(email_or_name)
122
+ @serv.logger.debug(whoami) { "search_debitors(#{email_or_name})" }
123
+ Debitor.search_by_exact_email(email_or_name) |
124
+ Debitor.search_by_exact_name(email_or_name)
125
+ end
126
+ def send_invoice(invoice_id, sort_args={})
127
+ @serv.logger.info(whoami) { "send_invoice(#{invoice_id})" }
128
+ Mail.send_invoice(@serv.config, invoice(invoice_id), sort_args)
129
+ end
130
+ def update_item(invoice_id, index, data, invoice_key=:invoice)
131
+ @serv.logger.debug(whoami) {
132
+ "update_item(#{invoice_id}, #{index}, #{data.inspect})" }
133
+ invoice = self.send(invoice_key, invoice_id)
134
+ item = invoice.item(index)
135
+ item.update(data)
136
+ invoice.odba_store
137
+ item
138
+ end
139
+ def whoami
140
+ @user.unique_id.to_s
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # User -- ydim -- 10.01.2006 -- hwyss@ywesee.com
3
+
4
+ require 'rrba/user'
5
+ require 'odba/drbwrapper'
6
+ require 'ydim/root_session'
7
+
8
+ module YDIM
9
+ class RootUser < RRBA::User
10
+ def new_session
11
+ ODBA::DRbWrapper.new(RootSession.new(self))
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env ruby
2
+ # Server -- ydim -- 10.01.2006 -- hwyss@ywesee.com
3
+
4
+ require 'logger'
5
+ require 'needle'
6
+ require 'odba/id_server'
7
+ require 'rrba/server'
8
+ require 'ydim/autoinvoicer'
9
+ require 'ydim/client'
10
+ require 'ydim/currency_converter'
11
+ require 'ydim/currency_updater'
12
+ require 'ydim/factory'
13
+ require 'ydim/root_user'
14
+ require 'ydim/util'
15
+
16
+ module YDIM
17
+ class Server
18
+ ydim_default_dir = File.join(ENV['HOME'], '.ydim')
19
+ default_config_files = [
20
+ File.join(ydim_default_dir, 'ydimd.yml'),
21
+ '/etc/ydim/ydimd.yml',
22
+ ]
23
+ defaults = {
24
+ 'autoinvoice_hour' => 1,
25
+ 'config' => default_config_files,
26
+ 'conf_dir' => File.join(ydim_default_dir, 'conf'),
27
+ 'currencies' => ['CHF', 'EUR', 'USD'],
28
+ 'currency_update_hour' => 2,
29
+ 'data_dir' => File.join(ydim_default_dir, 'data'),
30
+ 'server_url' => 'druby://localhost:12375',
31
+ 'db_driver_url' => 'DBI:Pg:ydim',
32
+ 'db_user' => 'ydim',
33
+ 'db_auth' => '',
34
+ 'detach' => false,
35
+ 'home_country' => 'Schweiz',
36
+ 'invoice_number_start' => 10000,
37
+ 'log_level' => 'INFO',
38
+ 'log_file' => STDOUT,
39
+ 'mail_body' => "%s %s\n%s",
40
+ 'mail_charset' => 'iso-8859-1',
41
+ 'mail_from' => '',
42
+ 'mail_recipients' => [],
43
+ 'root_name' => 'Root',
44
+ 'root_email' => '',
45
+ 'root_key' => 'root_dsa',
46
+ 'salutation' => {
47
+ '' => 'Sehr geehrter Herr',
48
+ 'Herr' => 'Sehr geehrter Herr',
49
+ 'Frau' => 'Sehr geehrte Frau',
50
+ },
51
+ 'smtp_from' => '',
52
+ 'smtp_authtype' => :plain,
53
+ 'smtp_domain' => 'ywesee.com',
54
+ 'smtp_pass' => nil,
55
+ 'smtp_port' => 587,
56
+ 'smtp_server' => 'localhost',
57
+ 'smtp_user' => 'ydim@ywesee.com',
58
+ 'vat_rate' => 7.6,
59
+ }
60
+ config = RCLConf::RCLConf.new(ARGV, defaults)
61
+ config.load(config.config)
62
+ CONFIG = config
63
+ SECONDS_IN_DAY = 24*60*60
64
+ def Server.config
65
+ CONFIG
66
+ end
67
+ def initialize(config, logger)
68
+ @serv = Needle::Registry.new
69
+ @serv.register(:auth_server) {
70
+ auth = RRBA::Server.new
71
+ root = RootUser.new(:root)
72
+ root.name = config.root_name
73
+ root.email = config.root_email
74
+ root_key = config.root_key
75
+ path = File.expand_path(root_key, config.conf_dir)
76
+ path_or_key = File.exist?(path) ? path : root_key
77
+ root.public_key = Util.load_key(path_or_key)
78
+ auth.root = root
79
+ auth
80
+ }
81
+ @serv.register(:clients) {
82
+ ClientHandler.new(@serv)
83
+ }
84
+ @serv.register(:config) {
85
+ config
86
+ }
87
+ @serv.register(:currency_converter) {
88
+ ODBA.cache.fetch_named('currency_converter', self) {
89
+ CurrencyConverter.new
90
+ }
91
+ }
92
+ @serv.register(:factory) {
93
+ Factory.new(@serv)
94
+ }
95
+ @serv.register(:id_server) {
96
+ ODBA.cache.fetch_named('id_server', self) {
97
+ ODBA::IdServer.new
98
+ }
99
+ }
100
+ @serv.register(:logger) {
101
+ logger
102
+ }
103
+ if(hour = config.autoinvoice_hour)
104
+ @autoinvoicer = repeat_at(hour, 'AutoInvoicer') {
105
+ AutoInvoicer.new(@serv).run
106
+ }
107
+ end
108
+ if(hour = config.currency_update_hour)
109
+ if(@serv.currency_converter.known_currencies \
110
+ < @serv.config.currencies.size)
111
+ CurrencyUpdater.new(@serv).run
112
+ end
113
+ @currency_updater = repeat_at(hour, 'CurrencyUpdater') {
114
+ CurrencyUpdater.new(@serv).run
115
+ }
116
+ end
117
+ @status_updater = repeat_at(1, 'StatusUpdater') {
118
+ Invoice.all { |inv| inv.save }
119
+ }
120
+ @sessions = []
121
+ end
122
+ def login(client, name=nil, &block)
123
+ @serv.logger.debug(client.__drburi) { 'attempting login' }
124
+ session = @serv.auth_server.authenticate(name, &block)
125
+ session.serv = @serv
126
+ session.client = client
127
+ @serv.logger.info(session.whoami) { 'login' }
128
+ @sessions.push(session)
129
+ session
130
+ rescue Exception => error
131
+ @serv.logger.error('unknown user') {
132
+ [error.class, error.message].join(' - ') }
133
+ raise
134
+ end
135
+ def logout(session)
136
+ @serv.logger.info(session.whoami) { 'logout' }
137
+ @sessions.delete(session)
138
+ nil
139
+ end
140
+ def ping
141
+ true
142
+ end
143
+ private
144
+ def repeat_at(hour, thread_name)
145
+ Thread.new {
146
+ Thread.current.abort_on_exception = true
147
+ loop {
148
+ now = Time.now
149
+ next_run = Time.local(now.year, now.month, now.day, hour)
150
+ sleepy_time = next_run - now
151
+ if(sleepy_time < 0)
152
+ sleepy_time += SECONDS_IN_DAY
153
+ next_run += SECONDS_IN_DAY
154
+ end
155
+ @serv.logger.info(thread_name) {
156
+ sprintf("next run %s, sleeping %i seconds",
157
+ next_run.strftime("%c"), sleepy_time)
158
+ }
159
+ sleep(sleepy_time)
160
+ yield
161
+ }
162
+ }
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,58 @@
1
+ require "openssl"
2
+ require "net/smtp"
3
+
4
+ Net::SMTP.class_eval do
5
+ private
6
+ def do_start(helodomain, user, secret, authtype)
7
+ raise IOError, 'SMTP session already started' if @started
8
+ check_auth_args user, secret, authtype if user or secret
9
+
10
+ sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
11
+ @socket = Net::InternetMessageIO.new(sock)
12
+ @socket.read_timeout = 60 #@read_timeout
13
+ @socket.debug_output = STDERR #@debug_output
14
+
15
+ check_response(critical { recv_response() })
16
+ do_helo(helodomain)
17
+
18
+ raise 'openssl library not installed' unless defined?(OpenSSL)
19
+ starttls
20
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
21
+ ssl.sync_close = true
22
+ ssl.connect
23
+ @socket = Net::InternetMessageIO.new(ssl)
24
+ @socket.read_timeout = 60 #@read_timeout
25
+ @socket.debug_output = STDERR #@debug_output
26
+ do_helo(helodomain)
27
+
28
+ authenticate user, secret, authtype if user
29
+ @started = true
30
+ ensure
31
+ unless @started
32
+ # authentication failed, cancel connection.
33
+ @socket.close if not @started and @socket and not @socket.closed?
34
+ @socket = nil
35
+ end
36
+ end
37
+
38
+ def do_helo(helodomain)
39
+ begin
40
+ if @esmtp
41
+ ehlo helodomain
42
+ else
43
+ helo helodomain
44
+ end
45
+ rescue Net::ProtocolError
46
+ if @esmtp
47
+ @esmtp = false
48
+ @error_occured = false
49
+ retry
50
+ end
51
+ raise
52
+ end
53
+ end
54
+
55
+ def starttls
56
+ getok('STARTTLS')
57
+ end
58
+ end
data/lib/ydim/util.rb ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # Util -- ydim -- 10.01.2006 -- hwyss@ywesee.com
3
+
4
+ require 'openssl'
5
+ require 'fileutils'
6
+
7
+ module YDIM
8
+ module Util
9
+ def Util.load_key(key)
10
+ if(File.exist?(key))
11
+ key = File.read(key)
12
+ end
13
+ key = OpenSSL::PKey::DSA.new(key)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,58 @@
1
+ <html><head><meta HTTP-EQUIV="content-type" CONTENT="text/html; charset=UTF-8"><title>1 USD in CHF - Google Search</title><style><!--
2
+ body,td,div,.p,a{font-family:arial,sans-serif }
3
+ div,td{color:#000}
4
+ .f{color:#6f6f6f}
5
+ .fl:link{color:#77c}
6
+ a:link,.w,a.w:link,.w a:link{color:#00c}
7
+ a:visited,.fl:visited{color:#551a8b}
8
+ a:active,.fl:active{color:#f00}
9
+ .t a:link,.t a:active,.t a:visited,.t{color:#000}
10
+ .t{background-color:#e5ecf9}
11
+ .k{background-color:#36c}
12
+ .j{width:34em}
13
+ .h{color:#36c;font-size:14px}
14
+ .i,.i:link{color:#a90a08}
15
+ .a,.a:link{color:#008000}
16
+ .z{display:none}
17
+ div.n{margin-top:1ex}
18
+ .n a{font-size:10pt;color:#000}
19
+ .n .i{font-size:10pt;font-weight:bold}
20
+ .q:visited,.q:link,.q:active,.q{color:#00c;}
21
+ .b{font-size:12pt;color:#00c;font-weight:bold}
22
+ .ch{cursor:pointer;cursor:hand}
23
+ .sem{display:inline;margin:0;font-size:100%;font-weight:inherit}
24
+ .e{margin-top:.75em;margin-bottom:.75em}
25
+ .g{margin-top:1em;margin-bottom:1em}
26
+ .m{color:#666666;font-size:83%}
27
+ .ml:link{color:#551a8b;}
28
+ .sm{display:block;margin-top:0px;margin-bottom:0px;margin-left:40px}
29
+ .bl{display:none}
30
+ .fl2,.fl2:link,.fl2:visited{color:#7777CC}
31
+ .fl2:active{color:#f00}
32
+ .gh{background-color:#FFFF88;}
33
+ -->
34
+ </style>
35
+ <script>
36
+ <!--
37
+ if(document.images){new Image().src="/url?sa=Q&q=1+USD+in+CHF&ct=q&ei=I8_gQ4bMF9DqiwHQ9JHlCQ&sig2=UD3Tzz5it4Ox-8P_bqGNQQ";}
38
+ function ss(w,id){window.status=w;return true;}
39
+ function cs(){window.status='';}
40
+ function rwt(el,ct,cd,sg){el.href="/url?sa=t&ct="+escape(ct)+"&cd="+escape(cd)+"&url="+escape(el.href).replace(/\+/g,"%2B")+"&ei=I8_gQ4bMF9DqiwHQ9JHlCQ"+sg;el.onmousedown="";return true;}
41
+ var q="1+USD+in+CHF";var eid="I8_gQ4bMF9DqiwHQ9JHlCQ";document.write("<style type=text/css>.bl{ display:inline !important;}</style>");function _inv(){if(window.location.hash=="#invalid"){var a=window.location.href;window.location.replace(a.substring(0,a.indexOf("#")));if(window.location.hash=="#invalid"){window.location.hash=""}if(window.location.hash==""||window.location.hash=="#"){window.location.reload(true)}}}_inv();function ga(o,e) {if (document.getElementById) {var a = o.id.substring(1); var p = "", r = "", t, f, h;var g = e.target;if (g) { t = g.id;f = g.parentNode;if (f) {p = f.id;h = f.parentNode;if (h)r = h.id;}} else {h = e.srcElement;f = h.parentNode;if (f)p = f.id;t = h.id;}if (t==a || p==a || r==a)return true;document.getElementById(a).href += "&ct=bg";top.location.href=document.getElementById(a).href}}
42
+ //-->
43
+ </script>
44
+ </head><body bgcolor=#ffffff onload="document.gs.reset();" topmargin=3 marginheight=3><table border=0 cellspacing=0 cellpadding=0 width=100%><tr><td align=right nowrap><font size=-1><b>hannes.wyss@gmail.com</b>&nbsp;|&nbsp;<a href="/searchhistory/?hl=en">Search History</a>&nbsp;|&nbsp;<a href="https://www.google.com/accounts/ManageAccount">My Account</a>&nbsp;|&nbsp;<a href="http://www.google.com/accounts/Logout?continue=http://www.google.com/search%3Fq%3D1%2BUSD%2Bin%2BCHF">Sign out</a></font></td></tr><tr height=4><td><img alt="" width=1 height=1></td></tr></table><table border=0 cellpadding=0 cellspacing=0 width=100%><tr><form name=gs method=GET action=/search><td valign=top><a href="http://www.google.com/webhp?hl=en"><img src="/images/logo_sm.gif" width=150 height=55 alt="Go to Google Home" border=0 vspace=12></a></td><td>&nbsp;&nbsp;</td><td valign=top width=100%><table cellpadding=0 cellspacing=0 border=0><tr><td height=14 valign=bottom><script><!--
45
+ function qs(el) {if (window.RegExp && window.encodeURIComponent) {var ue=el.href;var qe=encodeURIComponent(document.gs.q.value);if(ue.indexOf("q=")!=-1){el.href=ue.replace(new RegExp("q=[^&$]*"),"q="+qe);}else{el.href=ue+"&q="+qe;}}return 1;}
46
+ // -->
47
+ </script><table border=0 cellpadding=4 cellspacing=0><tr><td><font size=-1><b>Web</b>&nbsp;&nbsp;&nbsp;&nbsp;<a id=t1a class=q href="http://images.google.com/images?q=1+USD+in+CHF&sa=N&tab=wi" onClick="return qs(this);">Images</a>&nbsp;&nbsp;&nbsp;&nbsp;<a id=t2a class=q href="http://groups.google.com/groups?q=1+USD+in+CHF&sa=N&tab=wg" onClick="return qs(this);">Groups</a>&nbsp;&nbsp;&nbsp;&nbsp;<a id=t4a class=q href="http://news.google.com/news?q=1+USD+in+CHF&sa=N&tab=wn" onClick="return qs(this);">News</a>&nbsp;&nbsp;&nbsp;&nbsp;<a id=t5a class=q href="http://froogle.google.com/froogle?q=1+USD+in+CHF&sa=N&tab=wf" onClick="return qs(this);">Froogle</a>&nbsp;&nbsp;&nbsp;&nbsp;<a id=t7a class=q href="http://local.google.com/local?q=1+USD+in+CHF&sa=N&tab=wl" onClick="return qs(this);">Local</a>&nbsp;&nbsp;&nbsp;&nbsp;<b><a href="/intl/en/options/" class=q>more&nbsp;&raquo;</a></b></font></td></tr></table></td></tr><tr><td><table border=0 cellpadding=0 cellspacing=0><tr><td nowrap><input type=hidden name=hl value="en"><input type=hidden name=lr value=""><input type=hidden name=c2coff value=1><input type=text name=q size=41 maxlength=2048 value="1 USD in CHF" title="Search"><font size=-1> <input type=submit name="btnG" value="Search"><span id=hf></span></font></td><td nowrap><font size=-2>&nbsp;&nbsp;<a href=/advanced_search?q=1+USD+in+CHF&hl=en&lr=&c2coff=1>Advanced Search</a><br>&nbsp;&nbsp;<a href=/preferences?q=1+USD+in+CHF&hl=en&lr=&c2coff=1>Preferences</a>&nbsp;&nbsp;&nbsp;&nbsp;</font></td></tr></table></td></tr></table><table cellpadding=0 cellspacing=0 border=0><tr><td><font size=-1></font></td></tr><tr><td height=7><img width=1 height=1 alt=""></td></tr></table></td></form></tr></table><table width=100% border=0 cellpadding=0 cellspacing=0><tr><td bgcolor=#3366cc><img width=1 height=1 alt=""></td></tr></table><table width=100% border=0 cellpadding=0 cellspacing=0 bgcolor=#e5ecf9><tr><td bgcolor=#e5ecf9 width=1% nowrap><font size=+1>&nbsp;<b>Web</b></font>&nbsp;</td></tr></table>
48
+ <script><!--
49
+ var tt = document.gs.q;
50
+ tt.focus();
51
+ var r = tt.createTextRange;
52
+ if (r) {
53
+ var s = r();
54
+ s.collapse(false);
55
+ s.select(); }
56
+ // -->
57
+ </script>
58
+ <p><table><tr><td><img src=/images/calc_img.gif></td><td>&nbsp;</td><td nowrap><font size=+1><b>1 U.S. dollar = 1.2864003 Swiss francs</b></td></tr><tr><td>&nbsp;</td><td>&nbsp;</td><td><font size=-1><nobr>Rates provided for information only - <a href="/help/currency_disclaimer.html">see disclaimer</a>.</nobr>&nbsp;<a href=/help/features.html#currency>More about currency conversion.</a></td></tr></table><br><table><tr><td><font size=+0>Search for documents containing the terms <a href=/search?hl=en&lr=&c2coff=1&q=%2B1+USD+in+CHF&nocalc=1><b><i>1 USD in CHF</i></b></a>.</td></tr></table><br><br><br clear=all><center><p><hr class=z><table width=100% border=0 cellpadding=0 cellspacing=0><tr><td bgcolor=#3366cc colspan=2><img width=1 height=1 alt=""></td></tr></table><table width=100% cellpadding=2 cellspacing=0 border=0><tr><td align=center class=t><font size=-1><a href="http://www.google.com/">Google&nbsp;Home</a> - <a href="/ads/">Advertising&nbsp;Programs</a> - <a href="/services/">Business&nbsp;Solutions</a> - <a href=/about.html>About Google</a></font></table><br><font size=-1 class=p>&copy;2006 Google</font></center></body></html>
data/test/stub/odba.rb ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # ODBA Stub -- ydim -- 24.01.2005 -- hwyss@ywesee.com
3
+
4
+ require 'odba/odba'
5
+
6
+ module ODBA
7
+ def ODBA.transaction(&block)
8
+ block.call
9
+ end
10
+ module Persistable
11
+ attr_reader :odba_stored
12
+ def odba_store
13
+ @odba_stored = @odba_stored.to_i.next
14
+ end
15
+ end
16
+ class Cache
17
+ def fetch_named(*args, &block)
18
+ block.call
19
+ end
20
+ end
21
+ end
data/test/suite.rb ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # TestSuite -- ydim -- 27.01.2005 -- hwyss@ywesee.com
3
+
4
+ $: << File.dirname(File.expand_path(__FILE__))
5
+
6
+ Dir.foreach(File.dirname(__FILE__)) { |file|
7
+ if /^test_.*\.rb$/o.match(file)
8
+ require file
9
+ end
10
+ }
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # TestAutoInvoicer -- ydim -- 01.02.2007 -- hwyss@ywesee.com
3
+
4
+ $: << File.expand_path('../lib', File.dirname(__FILE__))
5
+
6
+ require 'test/unit'
7
+ require 'flexmock'
8
+ require 'ydim/autoinvoicer'
9
+ require 'ydim/invoice'
10
+
11
+ module YDIM
12
+ class TestAutoInvoicer < Test::Unit::TestCase
13
+ include FlexMock::TestCase
14
+ def setup
15
+ @serv = flexmock('Registry')
16
+ @autoinvoicer = AutoInvoicer.new(@serv)
17
+ end
18
+ def test_run
19
+ deb1 = flexmock('Debitor1')
20
+ deb1.should_receive(:autoinvoices).and_return([])
21
+ deb2 = flexmock('Debitor2')
22
+ inv1 = flexmock('AutoInvoice1')
23
+ inv1.should_receive(:total_netto).and_return(1)
24
+ inv1.should_receive(:date).and_return(Date.today)
25
+ inv2 = flexmock('AutoInvoice2')
26
+ inv2.should_receive(:total_netto).and_return(1)
27
+ inv2.should_receive(:date).and_return(Date.today >> 1)
28
+ inv3 = flexmock('AutoInvoice3')
29
+ inv3.should_receive(:total_netto).and_return(0)
30
+ deb2.should_receive(:autoinvoices).and_return([inv1, inv2, inv3])
31
+ debitors = { 0 => deb1, 1 => deb2 }
32
+ flexstub(Debitor).should_receive(:odba_extent).and_return { |blk|
33
+ debitors.each_value(&blk)
34
+ }
35
+ @serv.should_receive(:config).and_return('configuration')
36
+ factory = flexmock('Factory')
37
+ factory.should_receive(:generate_invoice).with(inv1)\
38
+ .times(1).and_return(:generated_invoice)
39
+ @serv.should_receive(:factory).and_return(factory)
40
+ ODBA.cache = cache = flexmock('ODBA')
41
+ cache.should_receive(:transaction).and_return { |bl| bl.call }
42
+ mail = flexstub(Mail)
43
+ mail.should_receive(:send_invoice)\
44
+ .with('configuration', :generated_invoice)\
45
+ .and_return { assert(true) }
46
+ mail.should_receive(:send_reminder).with('configuration', inv2)\
47
+ .and_return { assert(true) }
48
+ @autoinvoicer.run
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # TestCurrencyConverter -- ydim -- 01.02.2006 -- hwyss@ywesee.com
3
+
4
+ $: << File.expand_path('../lib', File.dirname(__FILE__))
5
+
6
+ require 'test/unit'
7
+ require 'ydim/currency_converter'
8
+
9
+ module YDIM
10
+ class TestCurrencyConverter < Test::Unit::TestCase
11
+ def setup
12
+ @converter = CurrencyConverter.new
13
+ end
14
+ def test_known_currencies
15
+ assert_equal(0, @converter.known_currencies)
16
+ @converter.store('EUR', 'CHF', 1.55474)
17
+ assert_equal(2, @converter.known_currencies)
18
+ @converter.store('USD', 'CHF', 1.28640)
19
+ assert_equal(3, @converter.known_currencies)
20
+ end
21
+ def test_convert
22
+ @converter.store('EUR', 'CHF', 1.55474)
23
+ assert_equal(1.55474, @converter.convert(1, 'EUR', 'CHF'))
24
+ assert_equal(1, @converter.convert(1, 'CHF', 'CHF'))
25
+ assert_equal(1/1.55474, @converter.convert(1, 'CHF', 'EUR'))
26
+ assert_raises(RuntimeError) {
27
+ @converter.convert(1, 'RND', 'CHF')
28
+ }
29
+ end
30
+ def test_drb_dup
31
+ @converter.store('EUR', 'CHF', 1.55474)
32
+ dup = @converter.drb_dup
33
+ assert_instance_of(MobileCurrencyConverter, dup)
34
+ assert_equal(1.55474, dup.convert(1, 'EUR', 'CHF'))
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+ # TestCurrencyUpdater -- ydim -- 01.02.2006 -- hwyss@ywesee.com
3
+
4
+
5
+ $: << File.expand_path('../lib', File.dirname(__FILE__))
6
+
7
+ require 'test/unit'
8
+ require 'ydim/currency_updater'
9
+ require 'flexmock'
10
+
11
+ module YDIM
12
+ class TestCurrencyUpdater < Test::Unit::TestCase
13
+ include FlexMock::TestCase
14
+ def setup
15
+ @serv = flexmock('Config')
16
+ @updater = CurrencyUpdater.new(@serv)
17
+ end
18
+ def test_extract_conversion
19
+ html = File.read(File.expand_path('data/search.html',
20
+ File.dirname(__FILE__)))
21
+ assert_equal('1.2864003', @updater.extract_conversion(html))
22
+ end
23
+ def test_run
24
+ resp = flexmock('HttpResponse')
25
+ resp.should_receive(:body)\
26
+ .and_return(File.read(File.expand_path('data/search.html',
27
+ File.dirname(__FILE__))))
28
+ session = flexmock('HttpSession')
29
+ session.should_receive(:get).with('/search?q=1+CHF+in+EUR')\
30
+ .and_return(resp)
31
+ flexstub(Net::HTTP).should_receive(:start)\
32
+ .times(1).and_return { |host, block|
33
+ block.call(session)
34
+ }
35
+ conv = flexmock('Converter')
36
+ conv.should_receive(:odba_store).times(1)
37
+ conv.should_receive(:store).times(1).times(1)
38
+ config = flexmock('Config')
39
+ config.should_receive(:currencies).and_return(['CHF', 'EUR'])
40
+ @serv.should_receive(:currency_converter).and_return(conv)
41
+ @serv.should_receive(:config).and_return(config)
42
+ @updater.run
43
+ end
44
+ end
45
+ end