ruby_tfs 0.0.3 → 0.1.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/lib/tfs/client.rb +1 -15
- data/lib/tfs/configuration.rb +22 -13
- data/ruby_tfs.gemspec +1 -1
- data/spec/fixtures/cassettes/builds.yml +44 -0
- data/spec/fixtures/cassettes/changeset_queries.yml +44 -0
- data/spec/fixtures/cassettes/changesets.yml +44 -0
- data/spec/fixtures/cassettes/project_changesets.yml +44 -0
- data/spec/fixtures/cassettes/project_workitems.yml +44 -0
- data/spec/fixtures/cassettes/projects.yml +44 -0
- data/spec/fixtures/cassettes/workitems.yml +44 -0
- data/spec/tfs/client_spec.rb +27 -4
- metadata +3 -3
data/lib/tfs/client.rb
CHANGED
@@ -9,11 +9,6 @@ module TFS
|
|
9
9
|
include TFS::Configuration
|
10
10
|
extend TFS::ClassHelpers
|
11
11
|
|
12
|
-
# Options specific to the provider (odata in this case)
|
13
|
-
PROVIDER_OPTIONS = [:username, :password, :verify_ssl]
|
14
|
-
|
15
|
-
attr_reader :connection, :endpoint
|
16
|
-
|
17
12
|
# Creates an instance of the client
|
18
13
|
def initialize(options={})
|
19
14
|
TFS::Configuration.keys.each do |key|
|
@@ -23,7 +18,7 @@ module TFS
|
|
23
18
|
|
24
19
|
# Creates the connection to the data provider source
|
25
20
|
def connect
|
26
|
-
@connection ||= @provider.new endpoint,
|
21
|
+
@connection ||= @provider.new endpoint, client_options
|
27
22
|
end
|
28
23
|
|
29
24
|
TFS::QueryEngine::VALID_CLASSES.each do |klass|
|
@@ -40,14 +35,5 @@ module TFS
|
|
40
35
|
return super unless @connection.respond_to? method_name
|
41
36
|
@connection.send(method_name, *args, &block)
|
42
37
|
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def opts_for_connection
|
47
|
-
{
|
48
|
-
username: @username,
|
49
|
-
password: @password
|
50
|
-
}.merge connection_options
|
51
|
-
end
|
52
38
|
end
|
53
39
|
end
|
data/lib/tfs/configuration.rb
CHANGED
@@ -5,21 +5,22 @@ module TFS
|
|
5
5
|
extend Forwardable
|
6
6
|
|
7
7
|
attr_writer :username, :password
|
8
|
-
attr_accessor :endpoint, :connection_options, :provider
|
8
|
+
attr_accessor :endpoint, :connection_options, :provider, :namespace
|
9
9
|
|
10
10
|
def_delegator :options, :hash
|
11
11
|
|
12
|
-
|
13
|
-
:
|
14
|
-
:
|
15
|
-
|
12
|
+
CONNECTION_DEFAULTS = {
|
13
|
+
rest_options: {
|
14
|
+
headers: {
|
15
|
+
user_agent: "TFS Ruby Gem"
|
16
|
+
},
|
17
|
+
request: {
|
18
|
+
open_timeout: 5,
|
19
|
+
timeout: 10,
|
20
|
+
},
|
16
21
|
},
|
17
|
-
:
|
18
|
-
|
19
|
-
:timeout => 10,
|
20
|
-
},
|
21
|
-
:verify_ssl => false
|
22
|
-
} unless defined? TFS::Configuration::CONNECTION_OPTIONS
|
22
|
+
verify_ssl: false
|
23
|
+
} unless defined? TFS::Configuration::CONNECTION_DEFAULTS
|
23
24
|
|
24
25
|
class << self
|
25
26
|
def keys
|
@@ -28,12 +29,13 @@ module TFS
|
|
28
29
|
:password,
|
29
30
|
:endpoint,
|
30
31
|
:connection_options,
|
31
|
-
:provider
|
32
|
+
:provider,
|
33
|
+
:namespace
|
32
34
|
]
|
33
35
|
end
|
34
36
|
|
35
37
|
def connection_options
|
36
|
-
|
38
|
+
CONNECTION_DEFAULTS
|
37
39
|
end
|
38
40
|
|
39
41
|
def username
|
@@ -55,6 +57,9 @@ module TFS
|
|
55
57
|
def options
|
56
58
|
Hash[Configuration.keys.map{|key| [key, send(key)]}]
|
57
59
|
end
|
60
|
+
|
61
|
+
# No default namespace
|
62
|
+
def namespace ; end
|
58
63
|
end
|
59
64
|
|
60
65
|
def configure
|
@@ -74,5 +79,9 @@ module TFS
|
|
74
79
|
def options
|
75
80
|
Hash[TFS::Configuration.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
|
76
81
|
end
|
82
|
+
|
83
|
+
def client_options
|
84
|
+
connection_options.merge(username: @username, password: @password, namespace: @namespace)
|
85
|
+
end
|
77
86
|
end
|
78
87
|
end
|
data/ruby_tfs.gemspec
CHANGED
@@ -494,4 +494,48 @@ http_interactions:
|
|
494
494
|
segment 'Builds'.</message>\r\n</error>"
|
495
495
|
http_version:
|
496
496
|
recorded_at: Wed, 06 Mar 2013 22:06:34 GMT
|
497
|
+
- request:
|
498
|
+
method: get
|
499
|
+
uri: https://codeplexodata.cloudapp.net/TFS29/$metadata
|
500
|
+
body:
|
501
|
+
encoding: US-ASCII
|
502
|
+
string: ''
|
503
|
+
headers:
|
504
|
+
Accept:
|
505
|
+
- application/json
|
506
|
+
Accept-Encoding:
|
507
|
+
- gzip, deflate
|
508
|
+
User-Agent:
|
509
|
+
- TFS Ruby Gem
|
510
|
+
response:
|
511
|
+
status:
|
512
|
+
code: 415
|
513
|
+
message: Unsupported Media Type
|
514
|
+
headers:
|
515
|
+
Cache-Control:
|
516
|
+
- private
|
517
|
+
Content-Length:
|
518
|
+
- '111'
|
519
|
+
Content-Type:
|
520
|
+
- application/json
|
521
|
+
Expires:
|
522
|
+
- Thu, 07 Mar 2013 19:09:30 GMT
|
523
|
+
Vary:
|
524
|
+
- ! '*'
|
525
|
+
Server:
|
526
|
+
- Microsoft-IIS/7.0
|
527
|
+
Dataserviceversion:
|
528
|
+
- 1.0;
|
529
|
+
X-Aspnet-Version:
|
530
|
+
- 4.0.30319
|
531
|
+
X-Powered-By:
|
532
|
+
- ASP.NET
|
533
|
+
Date:
|
534
|
+
- Thu, 07 Mar 2013 19:09:00 GMT
|
535
|
+
body:
|
536
|
+
encoding: US-ASCII
|
537
|
+
string: ! "{\r\n\"error\": {\r\n\"code\": \"\", \"message\": {\r\n\"lang\":
|
538
|
+
\"en-US\", \"value\": \"Unsupported media type requested.\"\r\n}\r\n}\r\n}"
|
539
|
+
http_version:
|
540
|
+
recorded_at: Thu, 07 Mar 2013 19:09:00 GMT
|
497
541
|
recorded_with: VCR 2.4.0
|
@@ -371,4 +371,48 @@ http_interactions:
|
|
371
371
|
\ </m:properties>\r\n </content>\r\n</entry>"
|
372
372
|
http_version:
|
373
373
|
recorded_at: Wed, 06 Mar 2013 16:49:31 GMT
|
374
|
+
- request:
|
375
|
+
method: get
|
376
|
+
uri: https://codeplexodata.cloudapp.net/TFS29/$metadata
|
377
|
+
body:
|
378
|
+
encoding: US-ASCII
|
379
|
+
string: ''
|
380
|
+
headers:
|
381
|
+
Accept:
|
382
|
+
- application/json
|
383
|
+
Accept-Encoding:
|
384
|
+
- gzip, deflate
|
385
|
+
User-Agent:
|
386
|
+
- TFS Ruby Gem
|
387
|
+
response:
|
388
|
+
status:
|
389
|
+
code: 415
|
390
|
+
message: Unsupported Media Type
|
391
|
+
headers:
|
392
|
+
Cache-Control:
|
393
|
+
- private
|
394
|
+
Content-Length:
|
395
|
+
- '111'
|
396
|
+
Content-Type:
|
397
|
+
- application/json
|
398
|
+
Expires:
|
399
|
+
- Thu, 07 Mar 2013 19:09:31 GMT
|
400
|
+
Vary:
|
401
|
+
- ! '*'
|
402
|
+
Server:
|
403
|
+
- Microsoft-IIS/7.0
|
404
|
+
Dataserviceversion:
|
405
|
+
- 1.0;
|
406
|
+
X-Aspnet-Version:
|
407
|
+
- 4.0.30319
|
408
|
+
X-Powered-By:
|
409
|
+
- ASP.NET
|
410
|
+
Date:
|
411
|
+
- Thu, 07 Mar 2013 19:09:01 GMT
|
412
|
+
body:
|
413
|
+
encoding: US-ASCII
|
414
|
+
string: ! "{\r\n\"error\": {\r\n\"code\": \"\", \"message\": {\r\n\"lang\":
|
415
|
+
\"en-US\", \"value\": \"Unsupported media type requested.\"\r\n}\r\n}\r\n}"
|
416
|
+
http_version:
|
417
|
+
recorded_at: Thu, 07 Mar 2013 19:09:01 GMT
|
374
418
|
recorded_with: VCR 2.4.0
|
@@ -234,4 +234,48 @@ http_interactions:
|
|
234
234
|
\ </m:properties>\r\n </content>\r\n </entry>\r\n</feed>"
|
235
235
|
http_version:
|
236
236
|
recorded_at: Wed, 06 Mar 2013 16:03:04 GMT
|
237
|
+
- request:
|
238
|
+
method: get
|
239
|
+
uri: https://codeplexodata.cloudapp.net/TFS29/$metadata
|
240
|
+
body:
|
241
|
+
encoding: US-ASCII
|
242
|
+
string: ''
|
243
|
+
headers:
|
244
|
+
Accept:
|
245
|
+
- application/json
|
246
|
+
Accept-Encoding:
|
247
|
+
- gzip, deflate
|
248
|
+
User-Agent:
|
249
|
+
- TFS Ruby Gem
|
250
|
+
response:
|
251
|
+
status:
|
252
|
+
code: 415
|
253
|
+
message: Unsupported Media Type
|
254
|
+
headers:
|
255
|
+
Cache-Control:
|
256
|
+
- private
|
257
|
+
Content-Length:
|
258
|
+
- '111'
|
259
|
+
Content-Type:
|
260
|
+
- application/json
|
261
|
+
Expires:
|
262
|
+
- Thu, 07 Mar 2013 19:09:31 GMT
|
263
|
+
Vary:
|
264
|
+
- ! '*'
|
265
|
+
Server:
|
266
|
+
- Microsoft-IIS/7.0
|
267
|
+
Dataserviceversion:
|
268
|
+
- 1.0;
|
269
|
+
X-Aspnet-Version:
|
270
|
+
- 4.0.30319
|
271
|
+
X-Powered-By:
|
272
|
+
- ASP.NET
|
273
|
+
Date:
|
274
|
+
- Thu, 07 Mar 2013 19:09:00 GMT
|
275
|
+
body:
|
276
|
+
encoding: US-ASCII
|
277
|
+
string: ! "{\r\n\"error\": {\r\n\"code\": \"\", \"message\": {\r\n\"lang\":
|
278
|
+
\"en-US\", \"value\": \"Unsupported media type requested.\"\r\n}\r\n}\r\n}"
|
279
|
+
http_version:
|
280
|
+
recorded_at: Thu, 07 Mar 2013 19:09:00 GMT
|
237
281
|
recorded_with: VCR 2.4.0
|
@@ -395,4 +395,48 @@ http_interactions:
|
|
395
395
|
\ </m:properties>\r\n </content>\r\n </entry>\r\n</feed>"
|
396
396
|
http_version:
|
397
397
|
recorded_at: Wed, 06 Mar 2013 21:20:34 GMT
|
398
|
+
- request:
|
399
|
+
method: get
|
400
|
+
uri: https://codeplexodata.cloudapp.net/TFS29/$metadata
|
401
|
+
body:
|
402
|
+
encoding: US-ASCII
|
403
|
+
string: ''
|
404
|
+
headers:
|
405
|
+
Accept:
|
406
|
+
- application/json
|
407
|
+
Accept-Encoding:
|
408
|
+
- gzip, deflate
|
409
|
+
User-Agent:
|
410
|
+
- TFS Ruby Gem
|
411
|
+
response:
|
412
|
+
status:
|
413
|
+
code: 415
|
414
|
+
message: Unsupported Media Type
|
415
|
+
headers:
|
416
|
+
Cache-Control:
|
417
|
+
- private
|
418
|
+
Content-Length:
|
419
|
+
- '111'
|
420
|
+
Content-Type:
|
421
|
+
- application/json
|
422
|
+
Expires:
|
423
|
+
- Thu, 07 Mar 2013 19:09:32 GMT
|
424
|
+
Vary:
|
425
|
+
- ! '*'
|
426
|
+
Server:
|
427
|
+
- Microsoft-IIS/7.0
|
428
|
+
Dataserviceversion:
|
429
|
+
- 1.0;
|
430
|
+
X-Aspnet-Version:
|
431
|
+
- 4.0.30319
|
432
|
+
X-Powered-By:
|
433
|
+
- ASP.NET
|
434
|
+
Date:
|
435
|
+
- Thu, 07 Mar 2013 19:09:01 GMT
|
436
|
+
body:
|
437
|
+
encoding: US-ASCII
|
438
|
+
string: ! "{\r\n\"error\": {\r\n\"code\": \"\", \"message\": {\r\n\"lang\":
|
439
|
+
\"en-US\", \"value\": \"Unsupported media type requested.\"\r\n}\r\n}\r\n}"
|
440
|
+
http_version:
|
441
|
+
recorded_at: Thu, 07 Mar 2013 19:09:01 GMT
|
398
442
|
recorded_with: VCR 2.4.0
|
@@ -790,4 +790,48 @@ http_interactions:
|
|
790
790
|
\ </m:properties>\r\n </content>\r\n </entry>\r\n</feed>"
|
791
791
|
http_version:
|
792
792
|
recorded_at: Wed, 06 Mar 2013 21:20:38 GMT
|
793
|
+
- request:
|
794
|
+
method: get
|
795
|
+
uri: https://codeplexodata.cloudapp.net/TFS29/$metadata
|
796
|
+
body:
|
797
|
+
encoding: US-ASCII
|
798
|
+
string: ''
|
799
|
+
headers:
|
800
|
+
Accept:
|
801
|
+
- application/json
|
802
|
+
Accept-Encoding:
|
803
|
+
- gzip, deflate
|
804
|
+
User-Agent:
|
805
|
+
- TFS Ruby Gem
|
806
|
+
response:
|
807
|
+
status:
|
808
|
+
code: 415
|
809
|
+
message: Unsupported Media Type
|
810
|
+
headers:
|
811
|
+
Cache-Control:
|
812
|
+
- private
|
813
|
+
Content-Length:
|
814
|
+
- '111'
|
815
|
+
Content-Type:
|
816
|
+
- application/json
|
817
|
+
Expires:
|
818
|
+
- Thu, 07 Mar 2013 19:09:32 GMT
|
819
|
+
Vary:
|
820
|
+
- ! '*'
|
821
|
+
Server:
|
822
|
+
- Microsoft-IIS/7.0
|
823
|
+
Dataserviceversion:
|
824
|
+
- 1.0;
|
825
|
+
X-Aspnet-Version:
|
826
|
+
- 4.0.30319
|
827
|
+
X-Powered-By:
|
828
|
+
- ASP.NET
|
829
|
+
Date:
|
830
|
+
- Thu, 07 Mar 2013 19:09:01 GMT
|
831
|
+
body:
|
832
|
+
encoding: US-ASCII
|
833
|
+
string: ! "{\r\n\"error\": {\r\n\"code\": \"\", \"message\": {\r\n\"lang\":
|
834
|
+
\"en-US\", \"value\": \"Unsupported media type requested.\"\r\n}\r\n}\r\n}"
|
835
|
+
http_version:
|
836
|
+
recorded_at: Thu, 07 Mar 2013 19:09:02 GMT
|
793
837
|
recorded_with: VCR 2.4.0
|
@@ -207,4 +207,48 @@ http_interactions:
|
|
207
207
|
\ </m:properties>\r\n </content>\r\n </entry>\r\n</feed>"
|
208
208
|
http_version:
|
209
209
|
recorded_at: Wed, 06 Mar 2013 00:34:13 GMT
|
210
|
+
- request:
|
211
|
+
method: get
|
212
|
+
uri: https://codeplexodata.cloudapp.net/TFS29/$metadata
|
213
|
+
body:
|
214
|
+
encoding: US-ASCII
|
215
|
+
string: ''
|
216
|
+
headers:
|
217
|
+
Accept:
|
218
|
+
- application/json
|
219
|
+
Accept-Encoding:
|
220
|
+
- gzip, deflate
|
221
|
+
User-Agent:
|
222
|
+
- TFS Ruby Gem
|
223
|
+
response:
|
224
|
+
status:
|
225
|
+
code: 415
|
226
|
+
message: Unsupported Media Type
|
227
|
+
headers:
|
228
|
+
Cache-Control:
|
229
|
+
- private
|
230
|
+
Content-Length:
|
231
|
+
- '111'
|
232
|
+
Content-Type:
|
233
|
+
- application/json
|
234
|
+
Expires:
|
235
|
+
- Thu, 07 Mar 2013 19:09:32 GMT
|
236
|
+
Vary:
|
237
|
+
- ! '*'
|
238
|
+
Server:
|
239
|
+
- Microsoft-IIS/7.0
|
240
|
+
Dataserviceversion:
|
241
|
+
- 1.0;
|
242
|
+
X-Aspnet-Version:
|
243
|
+
- 4.0.30319
|
244
|
+
X-Powered-By:
|
245
|
+
- ASP.NET
|
246
|
+
Date:
|
247
|
+
- Thu, 07 Mar 2013 19:09:02 GMT
|
248
|
+
body:
|
249
|
+
encoding: US-ASCII
|
250
|
+
string: ! "{\r\n\"error\": {\r\n\"code\": \"\", \"message\": {\r\n\"lang\":
|
251
|
+
\"en-US\", \"value\": \"Unsupported media type requested.\"\r\n}\r\n}\r\n}"
|
252
|
+
http_version:
|
253
|
+
recorded_at: Thu, 07 Mar 2013 19:09:02 GMT
|
210
254
|
recorded_with: VCR 2.4.0
|
@@ -2034,4 +2034,48 @@ http_interactions:
|
|
2034
2034
|
\ </m:properties>\r\n </content>\r\n</entry>"
|
2035
2035
|
http_version:
|
2036
2036
|
recorded_at: Tue, 05 Mar 2013 17:26:30 GMT
|
2037
|
+
- request:
|
2038
|
+
method: get
|
2039
|
+
uri: https://codeplexodata.cloudapp.net/TFS29/$metadata
|
2040
|
+
body:
|
2041
|
+
encoding: US-ASCII
|
2042
|
+
string: ''
|
2043
|
+
headers:
|
2044
|
+
Accept:
|
2045
|
+
- application/json
|
2046
|
+
Accept-Encoding:
|
2047
|
+
- gzip, deflate
|
2048
|
+
User-Agent:
|
2049
|
+
- TFS Ruby Gem
|
2050
|
+
response:
|
2051
|
+
status:
|
2052
|
+
code: 415
|
2053
|
+
message: Unsupported Media Type
|
2054
|
+
headers:
|
2055
|
+
Cache-Control:
|
2056
|
+
- private
|
2057
|
+
Content-Length:
|
2058
|
+
- '111'
|
2059
|
+
Content-Type:
|
2060
|
+
- application/json
|
2061
|
+
Expires:
|
2062
|
+
- Thu, 07 Mar 2013 19:09:33 GMT
|
2063
|
+
Vary:
|
2064
|
+
- ! '*'
|
2065
|
+
Server:
|
2066
|
+
- Microsoft-IIS/7.0
|
2067
|
+
Dataserviceversion:
|
2068
|
+
- 1.0;
|
2069
|
+
X-Aspnet-Version:
|
2070
|
+
- 4.0.30319
|
2071
|
+
X-Powered-By:
|
2072
|
+
- ASP.NET
|
2073
|
+
Date:
|
2074
|
+
- Thu, 07 Mar 2013 19:09:03 GMT
|
2075
|
+
body:
|
2076
|
+
encoding: US-ASCII
|
2077
|
+
string: ! "{\r\n\"error\": {\r\n\"code\": \"\", \"message\": {\r\n\"lang\":
|
2078
|
+
\"en-US\", \"value\": \"Unsupported media type requested.\"\r\n}\r\n}\r\n}"
|
2079
|
+
http_version:
|
2080
|
+
recorded_at: Thu, 07 Mar 2013 19:09:02 GMT
|
2037
2081
|
recorded_with: VCR 2.4.0
|
data/spec/tfs/client_spec.rb
CHANGED
@@ -9,10 +9,12 @@ describe TFS::Client do
|
|
9
9
|
username: "test", password: "123", provider: provider)
|
10
10
|
|
11
11
|
provider.should_receive(new: true).with("http://fake.tfs.net/tfs:8080",
|
12
|
-
{:
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
{:rest_options=>{
|
13
|
+
:headers=>{:user_agent=>"TFS Ruby Gem"},
|
14
|
+
:request=>{:open_timeout=>5, :timeout=>10}},
|
15
|
+
:verify_ssl=>false,
|
16
|
+
:username=>"test", :password=>"123",
|
17
|
+
:namespace=>nil}).once
|
16
18
|
|
17
19
|
client.connect
|
18
20
|
end
|
@@ -21,6 +23,27 @@ describe TFS::Client do
|
|
21
23
|
let(:client) { TFS::Client.new(endpoint: "https://codeplexodata.cloudapp.net/TFS29",
|
22
24
|
username: 'snd\plukevdh_cp', password: 'garbage') }
|
23
25
|
|
26
|
+
context "namespacing" do
|
27
|
+
use_vcr_cassette "changeset_queries"
|
28
|
+
|
29
|
+
it "should namespace if namespace provided" do
|
30
|
+
client = TFS::Client.new(endpoint: "https://codeplexodata.cloudapp.net/TFS29",
|
31
|
+
username: 'snd\plukevdh_cp', password: 'garbage', namespace: "MySpace")
|
32
|
+
|
33
|
+
client.connect
|
34
|
+
|
35
|
+
project = client.projects("rubytfs").run
|
36
|
+
project.first.class.name.should == "MySpace::Project"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "shouldn't namespace if no namespace given" do
|
40
|
+
client.connect
|
41
|
+
|
42
|
+
project = client.projects("rubytfs").run
|
43
|
+
project.first.class.name.should == "Project"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
24
47
|
context "individual queries" do
|
25
48
|
use_vcr_cassette "changeset_queries"
|
26
49
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_tfs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ruby_odata
|
@@ -96,7 +96,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
96
|
version: '0'
|
97
97
|
segments:
|
98
98
|
- 0
|
99
|
-
hash: -
|
99
|
+
hash: -1922355769199993263
|
100
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
101
|
none: false
|
102
102
|
requirements:
|