activefacts-api 1.8.1 → 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,577 +0,0 @@
1
- #
2
- # ActiveFacts tests: Constellation instances in the Runtime API
3
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
- #
5
-
6
- describe "A Constellation instance" do
7
- before :each do
8
- Object.send :remove_const, :Mod if Object.const_defined?("Mod")
9
- module Mod
10
- @base_types = [
11
- Int, Real, AutoCounter, String, Date, DateTime, Decimal, Guid
12
- ]
13
-
14
- # Create a value type and a subtype of that value type for each base type:
15
- @base_types.each do |base_type|
16
- Mod.module_eval <<-END
17
- class #{base_type.basename}Val < #{base_type.name}
18
- value_type
19
- end
20
-
21
- class #{base_type.basename}SubVal < #{base_type.name}Val
22
- # Note no new "value_type" is required here, it comes through inheritance
23
- end
24
- END
25
- end
26
-
27
- class Name < StringVal
28
- value_type
29
- #has_one :attr, Name
30
- has_one :undefined_role # This will be unsatisfied with the non-existence of the UndefinedRole class
31
- end
32
-
33
- class LegalEntity
34
- identified_by :name
35
- one_to_one :name, :mandatory => true
36
- end
37
-
38
- class Surrogate
39
- identified_by :auto_counter_val
40
- one_to_one :auto_counter_val
41
- end
42
-
43
- class Company < LegalEntity
44
- supertypes Surrogate
45
- end
46
-
47
- class Person < LegalEntity
48
- identified_by :name, :family_name # REVISIT: want a way to role_alias :name, :given_name
49
- supertypes :surrogate # Use a Symbol binding this time
50
-
51
- has_one :family_name, :class => Name
52
- has_one :employer, :class => Company
53
- one_to_one :birth_name, :class => Name
54
- end
55
- end
56
- @constellation = ActiveFacts::API::Constellation.new(Mod)
57
- end
58
-
59
- describe "Vocabulary" do
60
- it "should create the constellation" do
61
- Mod.constellation.should be_an_instance_of ActiveFacts::API::Constellation
62
- end
63
-
64
- it "should create the constellation by direct populate" do
65
- Mod.populate do
66
- Name "foo"
67
- end.should be_an_instance_of ActiveFacts::API::Constellation
68
- end
69
-
70
- it "should verbalise" do
71
- s = Mod.verbalise
72
- s.should be_an_instance_of String
73
- end
74
- end
75
-
76
- it "should allow creating a constellation" do
77
- @constellation = ActiveFacts::API::Constellation.new(Mod)
78
- end
79
-
80
- it "should complain when accessing a non-class as a method" do
81
- Mod::Foo = 23
82
- lambda { @constellation.Foo }.should raise_error(NoMethodError)
83
- end
84
-
85
- it "should complain when accessing a class that isn't an object type" do
86
- class Mod::Bar; end
87
- proc { @constellation.Bar }.should raise_error(NoMethodError)
88
- proc { @constellation.instances[Mod::Bar] }.should raise_error(ActiveFacts::API::InvalidObjectType)
89
- end
90
-
91
- it "should deny handling an object type defined outside the current module" do
92
- class ::Bar; end
93
- lambda { @constellation.Bar }.should raise_error(NoMethodError)
94
- lambda { @constellation.instances[Bar] }.should raise_error(ActiveFacts::API::InvalidObjectType)
95
- end
96
-
97
- it "should allow inspection" do
98
- lambda { @constellation.inspect }.should_not raise_error
99
- end
100
-
101
- it "should support fetching its vocabulary" do
102
- @constellation.vocabulary.should == Mod
103
- end
104
-
105
- # it "should support fetching its query" do
106
- # pending
107
- # @constellation.query.should == Mod
108
- # end
109
-
110
- it "should create methods to assert instances" do
111
- # Check that methods have not yet been created:
112
- @constellation.should_not respond_to(:Name)
113
- @constellation.should_not respond_to(:LegalEntity)
114
- @constellation.should_not respond_to(:Company)
115
- @constellation.should_not respond_to(:Person)
116
-
117
- # Assert instances
118
- name = foo = acme = fred_fly = nil
119
- lambda {
120
- name = @constellation.Name("foo")
121
- foo = @constellation.LegalEntity("foo")
122
- acme = @constellation.Company("Acme, Inc", :auto_counter_val => :new)
123
- fred_fly = @constellation.Person("fred", "fly", :auto_counter_val => :new)
124
- }.should_not raise_error
125
-
126
- # Check that methods have not yet been created:
127
- @constellation.should respond_to(:Name)
128
- @constellation.should respond_to(:LegalEntity)
129
- @constellation.should respond_to(:Company)
130
- @constellation.should respond_to(:Person)
131
-
132
- # Check the instances
133
- name.class.should == Mod::Name
134
- name.constellation.should == @constellation
135
-
136
- foo.class.should == Mod::LegalEntity
137
- foo.constellation.should == @constellation
138
- foo.inspect.should =~ / in Conste/
139
- foo.verbalise.should =~ /LegalEntity\(/
140
-
141
- acme.class.should == Mod::Company
142
- acme.constellation.should == @constellation
143
- acme.inspect.should =~ / in Conste/
144
- acme.verbalise.should =~ /Company\(/
145
-
146
- fred_fly.class.should == Mod::Person
147
- fred_fly.constellation.should == @constellation
148
- fred_fly.inspect.should =~ / in Conste/
149
- fred_fly.verbalise.should =~ /Person\(/
150
- end
151
-
152
- it "should re-use instances constructed the same way" do
153
- name1 = @constellation.Name("foo")
154
- foo1 = @constellation.LegalEntity("foo")
155
- acme1 = @constellation.Company("Acme, Inc", :auto_counter_val => :new)
156
- fred_fly1 = @constellation.Person("fred", "fly", :auto_counter_val => :new)
157
-
158
- name2 = @constellation.Name("foo")
159
- foo2 = @constellation.LegalEntity("foo")
160
- acme2 = @constellation.Company("Acme, Inc") # , :auto_counter_val => :new)
161
- fred_fly2 = @constellation.Person("fred", "fly") # , :auto_counter_val => :new)
162
-
163
- name1.object_id.should == name2.object_id
164
- foo1.object_id.should == foo2.object_id
165
- acme1.object_id.should == acme2.object_id
166
- fred_fly1.object_id.should == fred_fly2.object_id
167
- end
168
-
169
- describe "re-assertion with any one of multiple identifiers" do
170
- before :each do
171
- # Create some instances:
172
- @name1 = @constellation.Name("foo") # Value type
173
- @foo1 = @constellation.LegalEntity("foo") # Entity Type with simple identifier
174
- @acme1 = @constellation.Company("Acme, Inc", :auto_counter_val => :new)
175
- @acme1_id = @acme1.auto_counter_val
176
- end
177
-
178
- it "should be allowed with a normal value type id" do
179
- # Reassert the instances:
180
- @name2 = @constellation.Name("foo")
181
- @foo2 = @constellation.LegalEntity("foo")
182
- @acme2 = nil
183
- lambda {
184
- # Without the auto_counter_val
185
- @acme2 = @constellation.Company("Acme, Inc")
186
- }.should_not raise_error
187
-
188
- # This creates a new auto_counter_val, changing the acme instance (and hence, both references to it)
189
- @acme2.auto_counter_val = :new
190
- @acme2.should == @acme1
191
- @acme2.auto_counter_val.should_not be_defined
192
- @acme2.auto_counter_val.to_s.should_not == @acme1_id.to_s
193
- end
194
-
195
- it "should be allowed with an autocounter id" do
196
- acme3 = @constellation.Surrogate(@acme1_id)
197
- acme3.should == @acme1
198
- end
199
- end
200
-
201
- it "Should raise an exception with assigning a role whose referent (object type) has not yet been defined" do
202
- n = @constellation.Name("Fred")
203
- # This does not raise the "settable_roles_exception". I'm no longer sure how I did this, so I can't get coverage on this code :(
204
- proc { n.undefined_role = 'foo' }.should raise_error(NoMethodError)
205
- end
206
-
207
- # Maybe not complete yet
208
- describe "assigning additional arguments on asserting a value type" do
209
- before :each do
210
- # This should work, but...
211
- # birth_name = @constellation.Name("Smith", :person_as_birth_name => {:name => "Janet", :family_name => "Jones", :auto_counter_val => :new})
212
- # for now, use the following form
213
- @birth_name = @constellation.Name("Smith", :person_as_birth_name => ["Janet", "Jones", {:auto_counter_val => :new}])
214
- @person = @birth_name.person_as_birth_name
215
- end
216
-
217
- it "should create required instances" do
218
- @person.should_not be_nil
219
- @person.family_name.should == "Jones"
220
- @person.name.should == "Janet"
221
- @person.birth_name.should == "Smith"
222
- end
223
-
224
- it "should initialise secondary supertypes" do
225
- @acv = @person.auto_counter_val
226
- @acv.should_not be_nil
227
- @acv.surrogate.should == @person
228
- end
229
- end
230
-
231
- it "should support population blocks" do
232
- @constellation.populate do
233
- Name("bar")
234
- LegalEntity("foo")
235
- Person("Fred", "Nerk", :auto_counter_val => :new)
236
- Company("Acme, Inc", :auto_counter_val => :new)
237
- end
238
- @constellation.Name.size.should == 5
239
- @constellation.Surrogate.size.should == 2
240
- end
241
-
242
- it "should verbalise itself" do
243
- @constellation.populate do
244
- Name("bar")
245
- LegalEntity("foo")
246
- c = Company("Acme, Inc", :auto_counter_val => :new)
247
- c.is_a?(Mod::Surrogate).should == true
248
- c.auto_counter_val.should_not == nil
249
- p = Person("Fred", "Nerk", :auto_counter_val => :new)
250
- p.employer = c
251
- p.birth_name = "Nerk"
252
- end
253
- s = @constellation.verbalise
254
- names = s.split(/\n/).grep(/\tEvery /).map{|l| l.sub(/.*Every (.*):$/, '\1')}
255
- expected = ["Company", "LegalEntity", "Name", "Person", "StringVal", "Surrogate"]
256
- names.sort.should == expected
257
- end
258
-
259
- it "should support string capitalisation functions" do
260
- names = ["Company", "LegalEntity", "Name", "Person", "StringVal", "Surrogate"]
261
- camelwords = names.map{|n| n.camelwords }
262
- camelwords.should == [["Company"], ["Legal", "Entity"], ["Name"], ["Person"], ["String", "Val"], ["Surrogate"]]
263
-
264
- snakes = names.map{|n| n.snakecase }
265
- snakes.should == ["company", "legal_entity", "name", "person", "string_val", "surrogate"]
266
-
267
- camelupper = snakes.map{|n| n.camelcase }
268
- camelupper.should == ["Company", "LegalEntity", "Name", "Person", "StringVal", "Surrogate"]
269
-
270
- camellower = snakes.map{|n| n.camelcase(:lower) }
271
- camellower.should == ["company", "legalEntity", "name", "person", "stringVal", "surrogate"]
272
- end
273
-
274
- it "should allow inspection of instance indices" do
275
- baz = @constellation.Name("baz")
276
- @constellation.Name.inspect.class.should == String
277
- end
278
-
279
- it "should index value instances, including by its superclasses" do
280
- baz = @constellation.Name("baz")
281
- @constellation.Name.keys.sort.should == ["baz"]
282
-
283
- @constellation.StringVal.keys.sort.should == ["baz"]
284
- @constellation.StringVal[baz].should == baz
285
- @constellation.StringVal["baz"].should == baz
286
- end
287
-
288
- describe "instance indices" do
289
- it "should support each" do
290
- baz = @constellation.Name("baz")
291
- count = 0
292
- @constellation.Name.each { |k, v| count += 1 }
293
- count.should == 1
294
- end
295
-
296
- it "should support detect" do
297
- baz = @constellation.Name("baz")
298
- @constellation.Name.detect { |rv| true }.should be_truthy
299
- end
300
- end
301
-
302
- it "should index entity instances, including by its superclass and secondary supertypes" do
303
- name = "Acme, Inc"
304
- fred = "Fred"
305
- fly = "Fly"
306
- acme = @constellation.Company name, :auto_counter_val => :new
307
- fred_fly = @constellation.Person fred, fly, :auto_counter_val => :new
308
-
309
- # REVISIT: This should be illegal:
310
- #fred_fly.auto_counter_val = :new
311
-
312
- @constellation.Person.keys.sort.should == [[fred, fly]]
313
- @constellation.Company.keys.sort.should == [[name]]
314
-
315
- @constellation.LegalEntity.keys.sort.should include [name]
316
- @constellation.LegalEntity.keys.sort.should include [fred]
317
-
318
- @constellation.Surrogate.values.should include acme
319
- @constellation.Surrogate.values.should include fred_fly
320
- end
321
-
322
- it "should handle one-to-ones correctly" do
323
- person = @constellation.Person "Fred", "Smith", :auto_counter_val => :new, :birth_name => "Nerk"
324
-
325
- nerk = @constellation.Name["Nerk"]
326
- nerk.should_not be_nil
327
- nerk.person_as_birth_name.should == person
328
- person.birth_name = nil
329
- nerk.person_as_birth_name.should be_nil
330
- @constellation.Name["Nerk"].should_not be_nil
331
- end
332
-
333
- it "should allow retraction of instances" do
334
- @constellation.Person
335
- person = @constellation.Person "Fred", "Smith", :auto_counter_val => :new, :birth_name => "Nerk"
336
- smith = @constellation.Name("Smith")
337
-
338
- # Check things are indexed properly:
339
- @constellation.Surrogate.size.should == 1
340
- @constellation.LegalEntity.size.should == 1
341
- @constellation.Person.size.should == 1
342
- person.family_name.should == smith
343
- smith.all_person_as_family_name.size.should == 1
344
-
345
- @constellation.retract(smith)
346
-
347
- @constellation.Name["Fred"].should_not be_nil # FamilyName is not mandatory, so Fred still exists
348
- @constellation.Name["Smith"].should be_nil
349
-
350
- @constellation.Surrogate.size.should == 1
351
- @constellation.LegalEntity.size.should == 1
352
- @constellation.Person.size.should == 1
353
-
354
- person.family_name.should be_nil
355
-
356
- smith.all_person_as_family_name.size.should == 0
357
- end
358
-
359
- it "should retract linked instances (cascading)" do
360
- fred = @constellation.Person "Fred", "Smith", :auto_counter_val => :new, :birth_name => "Nerk"
361
- george = @constellation.Person "George", "Smith", :auto_counter_val => :new, :birth_name => "Patrick"
362
- smith = @constellation.Name("Smith")
363
-
364
- @constellation.Person.size.should == 2
365
- fred.family_name.should == smith
366
- george.family_name.should == smith
367
- smith.all_person_as_family_name.size.should == 2
368
-
369
- @constellation.retract(fred)
370
-
371
- @constellation.Person.size.should == 1 # Fred is gone, George still exists
372
- @constellation.Person.values[0].name.should == 'George'
373
- fred.family_name.should be_nil
374
- smith.all_person_as_family_name.size.should == 1
375
- end
376
-
377
- it "should fail to recognise references to unresolved forward referenced classes" do
378
- module Mod2
379
- class Foo
380
- identified_by :name
381
- one_to_one :name
382
- has_one :not_yet
383
- has_one :baz, :class => "BAZ"
384
- end
385
-
386
- class Name < String
387
- value_type
388
- end
389
- end
390
-
391
- @c = ActiveFacts::API::Constellation.new(Mod2)
392
- le = @c.Foo("Foo")
393
- lambda {
394
- le.not_yet
395
- }.should raise_error(NoMethodError)
396
- lambda {
397
- le.baz
398
- }.should raise_error(NoMethodError)
399
-
400
- # Now define the classes and try again:
401
- module Mod2
402
- class NotYet < String
403
- value_type
404
- end
405
- class BAZ < String
406
- value_type
407
- end
408
- end
409
- lambda {
410
- le.not_yet
411
- le.not_yet = 'not_yet'
412
- }.should_not raise_error
413
- lambda {
414
- le.baz
415
- le.baz = 'baz'
416
- }.should_not raise_error
417
- end
418
-
419
- it "should not allow references to classes outside the vocabulary" do
420
- module Outside
421
- class Other < String
422
- value_type
423
- end
424
- end
425
-
426
- lambda {
427
- module Mod
428
- class IntVal
429
- has_one :thingummy, :class => Outside::Other
430
- end
431
- end
432
- }.should raise_error(ActiveFacts::API::CrossVocabularyRoleException)
433
- end
434
-
435
- it "should disallow unrecognised supertypes" do
436
- lambda {
437
- module Mod
438
- class LegalEntity
439
- supertypes :foo
440
- end
441
- end
442
- }.should raise_error(NameError)
443
-
444
- lambda {
445
- module Mod
446
- class LegalEntity
447
- supertypes Bignum
448
- end
449
- end
450
- }.should raise_error(ActiveFacts::API::InvalidSupertypeException)
451
-
452
- lambda {
453
- module Mod
454
- class LegalEntity
455
- supertypes 3
456
- end
457
- end
458
- }.should raise_error(ActiveFacts::API::InvalidSupertypeException)
459
- end
460
-
461
- it "should allow supertypes with supertypes" do
462
- lambda {
463
- module Mod
464
- class ListedCompany < Company
465
- end
466
- end
467
- c = @constellation.ListedCompany("foo", :auto_counter_val => 23)
468
- }.should_not raise_error
469
- end
470
-
471
- it "should be able to attach a new supertype on an entity type to make it a (sub-)subtype" do
472
- module Mod
473
- class Dad
474
- identified_by :name
475
- end
476
- class Son < Dad
477
- identified_by :name
478
- end
479
- # the grand son will be linked on the fly
480
- class GrandSon
481
- identified_by :name
482
- end
483
- end
484
- Mod::GrandSon.supertypes(Mod::Son)
485
- Mod::GrandSon.supertypes.should include Mod::Son
486
- Mod::Son.supertypes.should include Mod::Dad
487
- end
488
-
489
- it "should keep information on where the identification came from" do
490
- module Mod
491
- class Dad
492
- identified_by :name
493
- end
494
- class Son < Dad
495
- identified_by :name
496
- end
497
- # Note the inheritance.
498
- class GrandSon < Son
499
- identified_by :name
500
- end
501
- end
502
-
503
- Mod::Son.identification_inherited_from.should == Mod::Dad
504
- Mod::Son.identification_inherited_from.should == Mod::Dad
505
- Mod::GrandSon.identification_inherited_from.should == Mod::Son
506
- Mod::GrandSon.overrides_identification_of.should == Mod::Dad
507
- end
508
-
509
- it "should disallow using a value type as a supertypes for an entity type" do
510
- lambda {
511
- module Mod
512
- class CompanyName
513
- identified_by :name
514
- supertypes :name
515
- end
516
- end
517
- }.should raise_error(ActiveFacts::API::InvalidSupertypeException)
518
- end
519
-
520
- it "should error on invalid :class values" do
521
- lambda {
522
- module Mod
523
- class Surrogate
524
- has_one :Name, :class => 3
525
- end
526
- end
527
- }.should raise_error(ArgumentError)
528
- end
529
-
530
- it "should error on misleading :class values" do
531
- lambda {
532
- module Mod
533
- class Surrogate
534
- has_one :Name, :class => Extra
535
- end
536
- end
537
- }.should raise_error(NameError)
538
- end
539
-
540
- it "should allow assert using an object of the same type" do
541
- c = @constellation.Company("foo", :auto_counter_val => 23)
542
- c2 = ActiveFacts::API::Constellation.new(Mod)
543
- lambda {
544
- c2.Company(c, :auto_counter_val => :new)
545
- }.should_not raise_error
546
- c2.Company.keys.should == [["foo"]]
547
- end
548
-
549
- it "should allow cross-constellation construction" do
550
- c = @constellation.Company("foo", :auto_counter_val => 23)
551
- lambda {
552
- c2 = ActiveFacts::API::Constellation.new(Mod)
553
- c2.Company(c.name, :auto_counter_val => :new)
554
- }.should_not raise_error
555
- end
556
-
557
- it "should copy values during cross-constellation assignment" do
558
- c = @constellation.Company("foo", :auto_counter_val => 23)
559
-
560
- # Now make a new constellation and use the above values to initialise new instances
561
- p = nil
562
- lambda {
563
- c2 = ActiveFacts::API::Constellation.new(Mod)
564
- p = c2.Person('Fred', 'Smith', :auto_counter_val => :new)
565
- p.employer = [ c.name, {:auto_counter_val => c.auto_counter_val}]
566
- }.should_not raise_error
567
- c.auto_counter_val.should_not === p.employer.auto_counter_val
568
- c.auto_counter_val.should_not == p.employer.auto_counter_val
569
- c.auto_counter_val.to_s.should == p.employer.auto_counter_val.to_s
570
- p.employer.should_not === c
571
-
572
- lambda {
573
- # Disallowed because it re-assigns the auto_counter_val identification value
574
- p.employer = [ "foo", {:auto_counter_val => :new}]
575
- }.should raise_error(ActiveFacts::API::TypeConflictException)
576
- end
577
- end