ollama_chat 0.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.
- checksums.yaml +7 -0
- data/.all_images.yml +17 -0
- data/.gitignore +9 -0
- data/Gemfile +5 -0
- data/README.md +159 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/bin/ollama_chat +5 -0
- data/lib/ollama_chat/chat.rb +398 -0
- data/lib/ollama_chat/clipboard.rb +23 -0
- data/lib/ollama_chat/dialog.rb +94 -0
- data/lib/ollama_chat/document_cache.rb +16 -0
- data/lib/ollama_chat/follow_chat.rb +60 -0
- data/lib/ollama_chat/information.rb +113 -0
- data/lib/ollama_chat/message_list.rb +216 -0
- data/lib/ollama_chat/message_type.rb +5 -0
- data/lib/ollama_chat/model_handling.rb +29 -0
- data/lib/ollama_chat/ollama_chat_config.rb +103 -0
- data/lib/ollama_chat/parsing.rb +159 -0
- data/lib/ollama_chat/source_fetching.rb +173 -0
- data/lib/ollama_chat/switches.rb +119 -0
- data/lib/ollama_chat/utils/cache_fetcher.rb +38 -0
- data/lib/ollama_chat/utils/chooser.rb +53 -0
- data/lib/ollama_chat/utils/fetcher.rb +175 -0
- data/lib/ollama_chat/utils/file_argument.rb +34 -0
- data/lib/ollama_chat/utils.rb +7 -0
- data/lib/ollama_chat/version.rb +8 -0
- data/lib/ollama_chat.rb +20 -0
- data/ollama_chat.gemspec +50 -0
- data/spec/assets/api_show.json +63 -0
- data/spec/assets/api_tags.json +21 -0
- data/spec/assets/conversation.json +14 -0
- data/spec/assets/duckduckgo.html +757 -0
- data/spec/assets/example.atom +26 -0
- data/spec/assets/example.csv +5 -0
- data/spec/assets/example.html +10 -0
- data/spec/assets/example.pdf +139 -0
- data/spec/assets/example.ps +4 -0
- data/spec/assets/example.rb +1 -0
- data/spec/assets/example.rss +25 -0
- data/spec/assets/example.xml +7 -0
- data/spec/assets/kitten.jpg +0 -0
- data/spec/assets/prompt.txt +1 -0
- data/spec/ollama_chat/chat_spec.rb +105 -0
- data/spec/ollama_chat/clipboard_spec.rb +29 -0
- data/spec/ollama_chat/follow_chat_spec.rb +46 -0
- data/spec/ollama_chat/information_spec.rb +50 -0
- data/spec/ollama_chat/message_list_spec.rb +132 -0
- data/spec/ollama_chat/model_handling_spec.rb +35 -0
- data/spec/ollama_chat/parsing_spec.rb +240 -0
- data/spec/ollama_chat/source_fetching_spec.rb +54 -0
- data/spec/ollama_chat/switches_spec.rb +167 -0
- data/spec/ollama_chat/utils/cache_fetcher_spec.rb +43 -0
- data/spec/ollama_chat/utils/fetcher_spec.rb +137 -0
- data/spec/ollama_chat/utils/file_argument_spec.rb +17 -0
- data/spec/spec_helper.rb +46 -0
- data/tmp/.keep +0 -0
- metadata +476 -0
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
RSpec.describe OllamaChat::Parsing do
|
5
|
+
let :chat do
|
6
|
+
OllamaChat::Chat.new
|
7
|
+
end
|
8
|
+
|
9
|
+
before do
|
10
|
+
stub_request(:get, %r(/api/tags\z)).
|
11
|
+
to_return(status: 200, body: asset_json('api_tags.json'))
|
12
|
+
stub_request(:post, %r(/api/show\z)).
|
13
|
+
to_return(status: 200, body: asset_json('api_show.json'))
|
14
|
+
chat
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#parse_source' do
|
18
|
+
it 'can parse HTML' do
|
19
|
+
asset_io('example.html') do |io|
|
20
|
+
def io.content_type
|
21
|
+
'text/html'
|
22
|
+
end
|
23
|
+
expect(chat.parse_source(io)).to eq(
|
24
|
+
"# My First Heading\n\nMy first paragraph.\n\n"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'can parse XML' do
|
30
|
+
asset_io('example.xml') do |io|
|
31
|
+
def io.content_type
|
32
|
+
'text/xml'
|
33
|
+
end
|
34
|
+
expect(chat.parse_source(io)).to eq(asset_content('example.xml'))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'can parse CSV' do
|
39
|
+
asset_io('example.csv') do |io|
|
40
|
+
def io.content_type
|
41
|
+
'text/csv'
|
42
|
+
end
|
43
|
+
expect(chat.parse_source(io)).to eq(<<EOT)
|
44
|
+
name: John Doe
|
45
|
+
age: 32
|
46
|
+
occupation: Software Engineer
|
47
|
+
|
48
|
+
name: Jane Smith
|
49
|
+
age: 28
|
50
|
+
occupation: Marketing Manager
|
51
|
+
|
52
|
+
name: Bob Johnson
|
53
|
+
age: 45
|
54
|
+
occupation: Retired
|
55
|
+
|
56
|
+
name: Alice Brown
|
57
|
+
age: 25
|
58
|
+
occupation: Student
|
59
|
+
|
60
|
+
EOT
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'can parse RSS' do
|
65
|
+
asset_io('example.rss') do |io|
|
66
|
+
def io.content_type
|
67
|
+
'application/rss+xml'
|
68
|
+
end
|
69
|
+
expect(chat.parse_source(io)).to start_with(<<~EOT)
|
70
|
+
# Example News Feed
|
71
|
+
|
72
|
+
## [New Study Shows Benefits of Meditation](https://example.com/article/meditation-benefits)
|
73
|
+
|
74
|
+
EOT
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'can parse RSS with content type XML' do
|
79
|
+
asset_io('example.rss') do |io|
|
80
|
+
def io.content_type
|
81
|
+
'text/xml'
|
82
|
+
end
|
83
|
+
expect(chat.parse_source(io)).to start_with(<<~EOT)
|
84
|
+
# Example News Feed
|
85
|
+
|
86
|
+
## [New Study Shows Benefits of Meditation](https://example.com/article/meditation-benefits)
|
87
|
+
|
88
|
+
EOT
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'can parse Atom' do
|
93
|
+
asset_io('example.atom') do |io|
|
94
|
+
def io.content_type
|
95
|
+
'application/atom+xml'
|
96
|
+
end
|
97
|
+
expect(chat.parse_source(io)).to start_with(<<~EOT)
|
98
|
+
# Example Feed
|
99
|
+
|
100
|
+
## [New Study Shows Benefits of Meditation](https://example.com/article/meditation-benefits)
|
101
|
+
|
102
|
+
updated on 2024-01-01T12:00:00Z
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
## [Local Business Opens New Location](https://example.com/article/local-business-new-location)
|
107
|
+
|
108
|
+
updated on 2024-01-02T10:00:00Z
|
109
|
+
EOT
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'can parse Postscript' do
|
114
|
+
asset_io('example.ps') do |io|
|
115
|
+
def io.content_type
|
116
|
+
'application/postscript'
|
117
|
+
end
|
118
|
+
expect(chat.parse_source(io)).to eq("Hello World!")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'can parse PDF' do
|
123
|
+
asset_io('example.pdf') do |io|
|
124
|
+
def io.content_type
|
125
|
+
'application/pdf'
|
126
|
+
end
|
127
|
+
expect(chat.parse_source(io)).to eq("Hello World!")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'can parse other texts' do
|
132
|
+
asset_io('example.rb') do |io|
|
133
|
+
def io.content_type
|
134
|
+
'application/x-ruby'
|
135
|
+
end
|
136
|
+
expect(chat.parse_source(io)).to eq(%{puts "Hello World!"\n})
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#parse_content' do
|
142
|
+
it 'can parse tags' do
|
143
|
+
content, tags = chat.parse_content("see #foobar …", [])
|
144
|
+
expect(content).to eq 'see #foobar …'
|
145
|
+
expect(tags).to include('foobar')
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'can parse https URLs' do
|
149
|
+
stub_request(:get, "https://www.example.com/foo.html").
|
150
|
+
with(headers: { 'Host' => 'www.example.com' }).
|
151
|
+
to_return(
|
152
|
+
status: 200,
|
153
|
+
body: "",
|
154
|
+
headers: { 'Content-Type' => 'text/html' }
|
155
|
+
)
|
156
|
+
content, = chat.parse_content('https://www.example.com/foo.html', [])
|
157
|
+
expect(content).to include 'Imported "https://www.example.com/foo.html"'
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'can parse file URLs' do
|
161
|
+
content, = chat.parse_content("see file://#{Dir.pwd}/spec/assets/example.html", [])
|
162
|
+
expect(content).to include(<<~EOT)
|
163
|
+
Imported "#{Pathname.pwd.join('spec/assets/example.html')}":
|
164
|
+
|
165
|
+
# My First Heading
|
166
|
+
|
167
|
+
My first paragraph.
|
168
|
+
EOT
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'can parse file paths' do
|
172
|
+
content, = chat.parse_content("see #{Dir.pwd}/spec/assets/example.html", [])
|
173
|
+
expect(content).to include(<<~EOT)
|
174
|
+
Imported "#{Pathname.pwd.join('spec/assets/example.html')}":
|
175
|
+
|
176
|
+
# My First Heading
|
177
|
+
|
178
|
+
My first paragraph.
|
179
|
+
EOT
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'can add images' do
|
183
|
+
images = []
|
184
|
+
expect(chat).to receive(:add_image).
|
185
|
+
with(images, kind_of(IO), %r(/spec/assets/kitten.jpg\z)).
|
186
|
+
and_call_original
|
187
|
+
chat.parse_content('./spec/assets/kitten.jpg', images)
|
188
|
+
expect(images.size).to eq 1
|
189
|
+
expect(images.first).to be_a Ollama::Image
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'document_policy' do
|
193
|
+
it 'can be ignoring' do
|
194
|
+
chat.document_policy = 'ignoring'
|
195
|
+
c = "see #{Dir.pwd}/spec/assets/example.html"
|
196
|
+
content, = chat.parse_content(c, [])
|
197
|
+
expect(content).to eq(c)
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'can be importing' do
|
201
|
+
chat.document_policy = 'importing'
|
202
|
+
c = "see #{Dir.pwd}/spec/assets/example.html"
|
203
|
+
content, = chat.parse_content(c, [])
|
204
|
+
expect(content).to include(<<~EOT)
|
205
|
+
Imported "#{Pathname.pwd.join('spec/assets/example.html')}":
|
206
|
+
|
207
|
+
# My First Heading
|
208
|
+
|
209
|
+
My first paragraph.
|
210
|
+
EOT
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'can be embedding' do
|
214
|
+
chat.document_policy = 'embedding'
|
215
|
+
c = "see #{Dir.pwd}/spec/assets/example.html"
|
216
|
+
expect(chat).to receive(:embed_source).with(
|
217
|
+
kind_of(IO),
|
218
|
+
Pathname.pwd.join('spec/assets/example.html').to_s
|
219
|
+
)
|
220
|
+
content, = chat.parse_content(c, [])
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'can be summarizing' do
|
224
|
+
chat.document_policy = 'summarizing'
|
225
|
+
c = "see #{Dir.pwd}/spec/assets/example.html"
|
226
|
+
content, = chat.parse_content(c, [])
|
227
|
+
expect(content).to start_with(<<~EOT)
|
228
|
+
see #{Pathname.pwd.join('spec/assets/example.html')}
|
229
|
+
|
230
|
+
Generate an abstract summary of the content in this document using
|
231
|
+
100 words:
|
232
|
+
|
233
|
+
# My First Heading
|
234
|
+
|
235
|
+
My first paragraph.
|
236
|
+
EOT
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe OllamaChat::SourceFetching do
|
4
|
+
let :chat do
|
5
|
+
OllamaChat::Chat.new
|
6
|
+
end
|
7
|
+
|
8
|
+
before do
|
9
|
+
stub_request(:get, %r(/api/tags\z)).
|
10
|
+
to_return(status: 200, body: asset_json('api_tags.json'))
|
11
|
+
stub_request(:post, %r(/api/show\z)).
|
12
|
+
to_return(status: 200, body: asset_json('api_show.json'))
|
13
|
+
allow(chat).to receive(:location).and_return(double(on?: false))
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'can import' do
|
17
|
+
expect(chat.import('./spec/assets/example.html')).to start_with(<<~EOT)
|
18
|
+
Imported "./spec/assets/example.html":
|
19
|
+
|
20
|
+
# My First Heading
|
21
|
+
|
22
|
+
My first paragraph.
|
23
|
+
EOT
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can summarize' do
|
27
|
+
expect(chat.summarize('./spec/assets/example.html')).to start_with(<<~EOT)
|
28
|
+
Generate an abstract summary of the content in this document using
|
29
|
+
100 words:
|
30
|
+
|
31
|
+
# My First Heading
|
32
|
+
|
33
|
+
My first paragraph.
|
34
|
+
EOT
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'can embed' do
|
38
|
+
expect(chat).to receive(:fetch_source).with(
|
39
|
+
'./spec/assets/example.html'
|
40
|
+
)
|
41
|
+
expect(chat.embed('./spec/assets/example.html')).to eq(
|
42
|
+
'This source was now embedded: ./spec/assets/example.html'
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can search web' do
|
47
|
+
stub_request(:get, "https://www.duckduckgo.com/html/?q=foo").
|
48
|
+
with(headers: { 'Host'=>'www.duckduckgo.com' }).
|
49
|
+
to_return(status: 200, body: asset_content('duckduckgo.html'), headers: {})
|
50
|
+
expect(chat.search_web('foo').first.to_s).to eq(
|
51
|
+
'https://en.wikipedia.org/wiki/Foo_Fighters'
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe OllamaChat::Switches do
|
4
|
+
describe OllamaChat::Switches::Switch do
|
5
|
+
let :switch do
|
6
|
+
described_class.new(
|
7
|
+
:test,
|
8
|
+
config: config,
|
9
|
+
msg: {
|
10
|
+
true => "Enabled.",
|
11
|
+
false => "Disabled.",
|
12
|
+
}
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'default to false' do
|
17
|
+
let :config do
|
18
|
+
double(test?: false)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
it 'can be switched on' do
|
23
|
+
expect {
|
24
|
+
switch.set(true)
|
25
|
+
}.to change {
|
26
|
+
switch.on? && !switch.off?
|
27
|
+
}.from(false).to(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can be toggled on' do
|
31
|
+
expect(STDOUT).to receive(:puts).with('Enabled.')
|
32
|
+
expect {
|
33
|
+
switch.toggle
|
34
|
+
}.to change {
|
35
|
+
switch.on? && !switch.off?
|
36
|
+
}.from(false).to(true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'default to false' do
|
41
|
+
let :config do
|
42
|
+
double(test?: true)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'can be switched on' do
|
46
|
+
expect {
|
47
|
+
switch.set(false)
|
48
|
+
}.to change {
|
49
|
+
switch.on? && !switch.off?
|
50
|
+
}.from(true).to(false)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'can be toggled off' do
|
54
|
+
expect(STDOUT).to receive(:puts).with('Disabled.')
|
55
|
+
expect {
|
56
|
+
switch.toggle
|
57
|
+
}.to change {
|
58
|
+
switch.on? && !switch.off?
|
59
|
+
}.from(true).to(false)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe OllamaChat::Switches::CombinedSwitch do
|
65
|
+
describe 'off' do
|
66
|
+
let :config do
|
67
|
+
double(test1?: true, test2?: false)
|
68
|
+
end
|
69
|
+
|
70
|
+
let :switch1 do
|
71
|
+
OllamaChat::Switches::Switch.new(
|
72
|
+
:test1,
|
73
|
+
config: config,
|
74
|
+
msg: {
|
75
|
+
true => "Enabled.",
|
76
|
+
false => "Disabled.",
|
77
|
+
}
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
let :switch2 do
|
82
|
+
OllamaChat::Switches::Switch.new(
|
83
|
+
:test2,
|
84
|
+
config: config,
|
85
|
+
msg: {
|
86
|
+
true => "Enabled.",
|
87
|
+
false => "Disabled.",
|
88
|
+
}
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
let :switch do
|
93
|
+
described_class.new(
|
94
|
+
value: -> { switch1.on? && switch2.off? },
|
95
|
+
msg: {
|
96
|
+
true => "Enabled.",
|
97
|
+
false => "Disabled.",
|
98
|
+
}
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'can be switched off 2' do
|
103
|
+
expect {
|
104
|
+
switch2.set(true)
|
105
|
+
}.to change {
|
106
|
+
switch.on? && !switch.off?
|
107
|
+
}.from(true).to(false)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'can be switched off 1' do
|
111
|
+
expect {
|
112
|
+
switch1.set(false)
|
113
|
+
}.to change {
|
114
|
+
switch.on? && !switch.off?
|
115
|
+
}.from(true).to(false)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe 'on' do
|
120
|
+
let :config do
|
121
|
+
double(test1?: false, test2?: true)
|
122
|
+
end
|
123
|
+
|
124
|
+
let :switch1 do
|
125
|
+
OllamaChat::Switches::Switch.new(
|
126
|
+
:test1,
|
127
|
+
config: config,
|
128
|
+
msg: {
|
129
|
+
true => "Enabled.",
|
130
|
+
false => "Disabled.",
|
131
|
+
}
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
let :switch2 do
|
136
|
+
OllamaChat::Switches::Switch.new(
|
137
|
+
:test2,
|
138
|
+
config: config,
|
139
|
+
msg: {
|
140
|
+
true => "Enabled.",
|
141
|
+
false => "Disabled.",
|
142
|
+
}
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
let :switch do
|
147
|
+
described_class.new(
|
148
|
+
value: -> { switch1.on? && switch2.off? },
|
149
|
+
msg: {
|
150
|
+
true => "Enabled.",
|
151
|
+
false => "Disabled.",
|
152
|
+
}
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'can be switched on' do
|
157
|
+
switch
|
158
|
+
expect {
|
159
|
+
switch1.set(true)
|
160
|
+
switch2.set(false)
|
161
|
+
}.to change {
|
162
|
+
switch.on? && !switch.off?
|
163
|
+
}.from(false).to(true)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe OllamaChat::Utils::CacheFetcher do
|
4
|
+
let :url do
|
5
|
+
'https://www.example.com/hello'
|
6
|
+
end
|
7
|
+
|
8
|
+
let :cache do
|
9
|
+
double('RedisCache')
|
10
|
+
end
|
11
|
+
|
12
|
+
let :fetcher do
|
13
|
+
described_class.new(cache).expose
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'can be instantiated' do
|
17
|
+
expect(fetcher).to be_a described_class
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'has #get' do
|
21
|
+
expect(cache).to receive(:[]).with('body-69ce405ab83f42dffa9fd22bbd47783f').and_return 'world'
|
22
|
+
expect(cache).to receive(:[]).with('content_type-69ce405ab83f42dffa9fd22bbd47783f').and_return 'text/plain'
|
23
|
+
yielded_io = nil
|
24
|
+
block = -> io { yielded_io = io }
|
25
|
+
fetcher.get(url, &block)
|
26
|
+
expect(yielded_io).to be_a StringIO
|
27
|
+
expect(yielded_io.read).to eq 'world'
|
28
|
+
end
|
29
|
+
|
30
|
+
it '#get needs block' do
|
31
|
+
expect { fetcher.get(url) }.to raise_error(ArgumentError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'has #put' do
|
35
|
+
io = StringIO.new('world')
|
36
|
+
io.extend(OllamaChat::Utils::Fetcher::HeaderExtension)
|
37
|
+
io.content_type = MIME::Types['text/plain'].first
|
38
|
+
io.ex = 666
|
39
|
+
expect(cache).to receive(:set).with('body-69ce405ab83f42dffa9fd22bbd47783f', 'world', ex: 666)
|
40
|
+
expect(cache).to receive(:set).with('content_type-69ce405ab83f42dffa9fd22bbd47783f', 'text/plain', ex: 666)
|
41
|
+
fetcher.put(url, io)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe OllamaChat::Utils::Fetcher do
|
4
|
+
let :url do
|
5
|
+
'https://www.example.com/hello'
|
6
|
+
end
|
7
|
+
|
8
|
+
let :fetcher do
|
9
|
+
described_class.new.expose
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can be instantiated' do
|
13
|
+
expect(fetcher).to be_a described_class
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has .get' do
|
17
|
+
expect(described_class).to receive(:new).and_return double(get: true)
|
18
|
+
described_class.get(url)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can #get with streaming' do
|
22
|
+
stub_request(:get, url).
|
23
|
+
with(headers: fetcher.headers).
|
24
|
+
to_return(
|
25
|
+
status: 200,
|
26
|
+
body: 'world',
|
27
|
+
headers: { 'Content-Type' => 'text/plain' },
|
28
|
+
)
|
29
|
+
fetcher.get(url) do |tmp|
|
30
|
+
expect(tmp).to be_a Tempfile
|
31
|
+
expect(tmp.read).to eq 'world'
|
32
|
+
expect(tmp.content_type).to eq 'text/plain'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'can #get without ssl peer verification' do
|
37
|
+
fetcher = described_class.new(
|
38
|
+
http_options: { ssl_verify_peer: false }
|
39
|
+
).expose
|
40
|
+
stub_request(:get, url).
|
41
|
+
with(headers: fetcher.headers).
|
42
|
+
to_return(
|
43
|
+
status: 200,
|
44
|
+
body: 'world',
|
45
|
+
headers: { 'Content-Type' => 'text/plain' },
|
46
|
+
)
|
47
|
+
expect(Excon).to receive(:new).with(
|
48
|
+
url,
|
49
|
+
hash_including(ssl_verify_peer: false)
|
50
|
+
).and_call_original
|
51
|
+
fetcher.get(url) do |tmp|
|
52
|
+
expect(tmp).to be_a Tempfile
|
53
|
+
expect(tmp.read).to eq 'world'
|
54
|
+
expect(tmp.content_type).to eq 'text/plain'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'can #get and fallback from streaming' do
|
59
|
+
stub_request(:get, url).
|
60
|
+
with(headers: fetcher.headers).
|
61
|
+
to_return(
|
62
|
+
{ status: 501 },
|
63
|
+
{
|
64
|
+
status: 200,
|
65
|
+
body: 'world',
|
66
|
+
headers: { 'Content-Type' => 'text/plain' },
|
67
|
+
}
|
68
|
+
)
|
69
|
+
fetcher.get(url) do |tmp|
|
70
|
+
expect(tmp).to be_a Tempfile
|
71
|
+
expect(tmp.read).to eq 'world'
|
72
|
+
expect(tmp.content_type).to eq 'text/plain'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'can #get and finally fail' do
|
77
|
+
stub_request(:get, url).
|
78
|
+
with(headers: fetcher.headers).
|
79
|
+
to_return(status: 500)
|
80
|
+
expect(STDERR).to receive(:puts).with(/cannot.*get.*#{url}/i)
|
81
|
+
fetcher.get(url) do |tmp|
|
82
|
+
expect(tmp).to be_a StringIO
|
83
|
+
expect(tmp.read).to eq ''
|
84
|
+
expect(tmp.content_type).to eq 'text/plain'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'can redirect' do
|
89
|
+
expect(fetcher.middlewares).to include Excon::Middleware::RedirectFollower
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'can .read' do
|
93
|
+
described_class.read(__FILE__) do |file|
|
94
|
+
expect(file).to be_a File
|
95
|
+
expect(file.read).to include 'can .read'
|
96
|
+
expect(file.content_type).to eq 'application/x-ruby'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'can .execute' do
|
101
|
+
described_class.execute('echo -n hello world') do |file|
|
102
|
+
expect(file).to be_a Tempfile
|
103
|
+
expect(file.read).to eq 'hello world'
|
104
|
+
expect(file.content_type).to eq 'text/plain'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'can .execute and fail' do
|
109
|
+
expect(IO).to receive(:popen).and_raise StandardError
|
110
|
+
expect(STDERR).to receive(:puts).with(/cannot.*execute.*foobar/i)
|
111
|
+
described_class.execute('foobar') do |file|
|
112
|
+
expect(file).to be_a StringIO
|
113
|
+
expect(file.read).to be_empty
|
114
|
+
expect(file.content_type).to eq 'text/plain'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '.normalize_url' do
|
119
|
+
it 'can handle umlauts' do
|
120
|
+
expect(described_class.normalize_url('https://foo.de/bär')).to eq(
|
121
|
+
'https://foo.de/b%C3%A4r'
|
122
|
+
)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'can handle escaped umlauts' do
|
126
|
+
expect(described_class.normalize_url('https://foo.de/b%C3%A4r')).to eq(
|
127
|
+
'https://foo.de/b%C3%A4r'
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'can remove #anchors' do
|
132
|
+
expect(described_class.normalize_url('https://foo.de#bar')).to eq(
|
133
|
+
'https://foo.de'
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe OllamaChat::Utils::FileArgument do
|
4
|
+
it 'it can return content' do
|
5
|
+
expect(described_class.get_file_argument('foo')).to eq 'foo'
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'it can return content at path' do
|
9
|
+
expect(described_class.get_file_argument(asset('prompt.txt'))).to include\
|
10
|
+
'test prompt'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'it can return default content' do
|
14
|
+
expect(described_class.get_file_argument('', default: 'foo')).to eq 'foo'
|
15
|
+
expect(described_class.get_file_argument(nil, default: 'foo')).to eq 'foo'
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
if ENV['START_SIMPLECOV'].to_i == 1
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter "#{File.basename(File.dirname(__FILE__))}/"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
require 'rspec'
|
8
|
+
require 'tins/xt/expose'
|
9
|
+
begin
|
10
|
+
require 'debug'
|
11
|
+
rescue LoadError
|
12
|
+
end
|
13
|
+
require 'webmock/rspec'
|
14
|
+
WebMock.disable_net_connect!
|
15
|
+
require 'ollama_chat'
|
16
|
+
|
17
|
+
def asset(name)
|
18
|
+
File.join(__dir__, 'assets', name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def asset_content(name)
|
22
|
+
File.read(File.join(__dir__, 'assets', name))
|
23
|
+
end
|
24
|
+
|
25
|
+
def asset_io(name, &block)
|
26
|
+
io = File.new(File.join(__dir__, 'assets', name))
|
27
|
+
if block
|
28
|
+
begin
|
29
|
+
block.call(io)
|
30
|
+
ensure
|
31
|
+
io.close
|
32
|
+
end
|
33
|
+
else
|
34
|
+
io
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def asset_json(name)
|
39
|
+
JSON(JSON(File.read(asset(name))))
|
40
|
+
end
|
41
|
+
|
42
|
+
RSpec.configure do |config|
|
43
|
+
config.before(:suite) do
|
44
|
+
infobar.show = nil
|
45
|
+
end
|
46
|
+
end
|
data/tmp/.keep
ADDED
File without changes
|