lluminary 0.1.1 → 0.1.2
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 +4 -4
- data/lib/lluminary/config.rb +11 -1
- data/lib/lluminary/field_description.rb +34 -29
- data/lib/lluminary/provider_error.rb +2 -1
- data/lib/lluminary/providers/base.rb +5 -1
- data/lib/lluminary/providers/bedrock.rb +32 -30
- data/lib/lluminary/providers/openai.rb +28 -21
- data/lib/lluminary/providers/test.rb +9 -14
- data/lib/lluminary/result.rb +5 -2
- data/lib/lluminary/schema.rb +9 -16
- data/lib/lluminary/schema_model.rb +13 -10
- data/lib/lluminary/task.rb +84 -59
- data/lib/lluminary/validation_error.rb +2 -1
- data/lib/lluminary/version.rb +3 -2
- data/lib/lluminary.rb +23 -7
- data/spec/examples/analyze_text_spec.rb +7 -4
- data/spec/examples/color_analyzer_spec.rb +22 -22
- data/spec/examples/content_analyzer_spec.rb +27 -44
- data/spec/examples/historical_event_analyzer_spec.rb +18 -15
- data/spec/examples/price_analyzer_spec.rb +22 -28
- data/spec/examples/quote_task_spec.rb +9 -8
- data/spec/examples/sentiment_analysis_spec.rb +13 -10
- data/spec/examples/summarize_text_spec.rb +7 -4
- data/spec/lluminary/config_spec.rb +28 -26
- data/spec/lluminary/field_description_spec.rb +19 -21
- data/spec/lluminary/providers/base_spec.rb +11 -8
- data/spec/lluminary/providers/bedrock_spec.rb +47 -57
- data/spec/lluminary/providers/openai_spec.rb +27 -35
- data/spec/lluminary/providers/test_spec.rb +21 -16
- data/spec/lluminary/result_spec.rb +17 -10
- data/spec/lluminary/schema_model_spec.rb +31 -22
- data/spec/lluminary/schema_spec.rb +95 -107
- data/spec/lluminary/task_spec.rb +366 -300
- data/spec/spec_helper.rb +7 -2
- metadata +35 -19
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
require_relative "../../examples/summarize_text"
|
3
4
|
|
4
5
|
RSpec.describe SummarizeText do
|
5
|
-
let(:text) {
|
6
|
+
let(:text) { <<~TEXT }
|
7
|
+
Ruby is a dynamic, open source programming language with a focus on simplicity and productivity. It has an elegant syntax that is natural to read and easy to write.
|
8
|
+
TEXT
|
6
9
|
|
7
10
|
it "summarizes text" do
|
8
11
|
result = described_class.call(text: text)
|
@@ -18,4 +21,4 @@ RSpec.describe SummarizeText do
|
|
18
21
|
expect(json).to have_key("summary")
|
19
22
|
expect(json["summary"]).to eq(result.output.summary)
|
20
23
|
end
|
21
|
-
end
|
24
|
+
end
|
@@ -1,47 +1,49 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
2
3
|
|
3
4
|
RSpec.describe Lluminary::Config do
|
4
5
|
let(:config) { described_class.new }
|
5
6
|
|
6
|
-
describe
|
7
|
-
it
|
7
|
+
describe "#configure" do
|
8
|
+
it "allows setting provider configurations" do
|
8
9
|
config.configure do |c|
|
9
|
-
c.provider(:openai, api_key:
|
10
|
-
c.provider(
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
c.provider(:openai, api_key: "global-key")
|
11
|
+
c.provider(
|
12
|
+
:bedrock,
|
13
|
+
access_key_id: "global-access-key",
|
14
|
+
secret_access_key: "global-secret-key",
|
15
|
+
region: "us-east-1"
|
14
16
|
)
|
15
17
|
end
|
16
18
|
|
17
|
-
expect(config.provider_config(:openai)).to eq({ api_key:
|
18
|
-
expect(config.provider_config(:bedrock)).to eq(
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
expect(config.provider_config(:openai)).to eq({ api_key: "global-key" })
|
20
|
+
expect(config.provider_config(:bedrock)).to eq(
|
21
|
+
{
|
22
|
+
access_key_id: "global-access-key",
|
23
|
+
secret_access_key: "global-secret-key",
|
24
|
+
region: "us-east-1"
|
25
|
+
}
|
26
|
+
)
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
26
|
-
describe
|
27
|
-
it
|
30
|
+
describe "#provider_config" do
|
31
|
+
it "returns empty hash for unconfigured providers" do
|
28
32
|
expect(config.provider_config(:unknown)).to eq({})
|
29
33
|
end
|
30
34
|
|
31
|
-
it
|
32
|
-
config.configure
|
33
|
-
c.provider(:openai, api_key: 'test-key')
|
34
|
-
end
|
35
|
+
it "returns the configuration for a configured provider" do
|
36
|
+
config.configure { |c| c.provider(:openai, api_key: "test-key") }
|
35
37
|
|
36
|
-
expect(config.provider_config(:openai)).to eq({ api_key:
|
38
|
+
expect(config.provider_config(:openai)).to eq({ api_key: "test-key" })
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
40
|
-
describe
|
41
|
-
it
|
42
|
+
describe "#reset!" do
|
43
|
+
it "clears all provider configurations" do
|
42
44
|
config.configure do |c|
|
43
|
-
c.provider(:openai, api_key:
|
44
|
-
c.provider(:bedrock, access_key_id:
|
45
|
+
c.provider(:openai, api_key: "test-key")
|
46
|
+
c.provider(:bedrock, access_key_id: "test-access-key")
|
45
47
|
end
|
46
48
|
|
47
49
|
config.reset!
|
@@ -50,4 +52,4 @@ RSpec.describe Lluminary::Config do
|
|
50
52
|
expect(config.provider_config(:bedrock)).to eq({})
|
51
53
|
end
|
52
54
|
end
|
53
|
-
end
|
55
|
+
end
|
@@ -1,36 +1,34 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
2
3
|
|
3
4
|
RSpec.describe Lluminary::FieldDescription do
|
4
|
-
describe
|
5
|
-
it
|
6
|
-
field = {
|
7
|
-
|
8
|
-
|
9
|
-
}
|
10
|
-
description = described_class.new('test_field', field)
|
11
|
-
expect(description.to_s).to eq('test_field (string): A test field')
|
5
|
+
describe "#to_s" do
|
6
|
+
it "generates a description for a string field" do
|
7
|
+
field = { type: :string, description: "A test field" }
|
8
|
+
description = described_class.new("test_field", field)
|
9
|
+
expect(description.to_s).to eq("test_field (string): A test field")
|
12
10
|
end
|
13
11
|
|
14
|
-
it
|
15
|
-
field = {
|
16
|
-
|
17
|
-
|
18
|
-
description = described_class.new('count', field)
|
19
|
-
expect(description.to_s).to eq('count (integer)')
|
12
|
+
it "generates a description for a field without a description" do
|
13
|
+
field = { type: :integer }
|
14
|
+
description = described_class.new("count", field)
|
15
|
+
expect(description.to_s).to eq("count (integer)")
|
20
16
|
end
|
21
17
|
|
22
|
-
it
|
18
|
+
it "includes validation descriptions when present" do
|
23
19
|
field = {
|
24
20
|
type: :string,
|
25
|
-
description:
|
21
|
+
description: "A test field",
|
26
22
|
validations: [
|
27
23
|
[{}, { length: { minimum: 5, maximum: 10 } }],
|
28
|
-
[{}, { format: { with:
|
24
|
+
[{}, { format: { with: "/^[A-Z]+$/" } }]
|
29
25
|
]
|
30
26
|
}
|
31
|
-
description = described_class.new(
|
32
|
-
expected =
|
27
|
+
description = described_class.new("test_field", field)
|
28
|
+
expected = <<~DESCRIPTION.chomp
|
29
|
+
test_field (string): A test field (must be at least 5 characters, must be at most 10 characters, must match format: /^[A-Z]+$/)
|
30
|
+
DESCRIPTION
|
33
31
|
expect(description.to_s).to eq(expected)
|
34
32
|
end
|
35
33
|
end
|
36
|
-
end
|
34
|
+
end
|
@@ -1,17 +1,20 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "lluminary"
|
2
3
|
|
3
4
|
RSpec.describe Lluminary::Providers::Base do
|
4
|
-
describe
|
5
|
-
it
|
6
|
-
config = { api_key:
|
5
|
+
describe "#initialize" do
|
6
|
+
it "accepts configuration options" do
|
7
|
+
config = { api_key: "test_key", model: "test_model" }
|
7
8
|
provider = described_class.new(**config)
|
8
9
|
expect(provider.config).to eq(config)
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
12
|
-
describe
|
13
|
-
it
|
14
|
-
expect
|
13
|
+
describe "#call" do
|
14
|
+
it "raises NotImplementedError" do
|
15
|
+
expect do
|
16
|
+
described_class.new.call("test", double("Task"))
|
17
|
+
end.to raise_error(NotImplementedError)
|
15
18
|
end
|
16
19
|
end
|
17
|
-
end
|
20
|
+
end
|
@@ -1,109 +1,99 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
require "lluminary/providers/bedrock"
|
3
4
|
|
4
5
|
RSpec.describe Lluminary::Providers::Bedrock do
|
5
6
|
let(:config) do
|
6
7
|
{
|
7
|
-
region:
|
8
|
-
access_key_id:
|
9
|
-
secret_access_key:
|
8
|
+
region: "us-east-1",
|
9
|
+
access_key_id: "test-key",
|
10
|
+
secret_access_key: "test-secret"
|
10
11
|
}
|
11
12
|
end
|
12
13
|
let(:provider) { described_class.new(**config) }
|
13
14
|
|
14
|
-
describe
|
15
|
-
it
|
15
|
+
describe "#client" do
|
16
|
+
it "returns the AWS Bedrock client instance" do
|
16
17
|
expect(provider.client).to be_a(Aws::BedrockRuntime::Client)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
|
-
describe
|
21
|
-
let(:prompt) {
|
22
|
-
let(:task) {
|
21
|
+
describe "#call" do
|
22
|
+
let(:prompt) { "Test prompt" }
|
23
|
+
let(:task) { "Test task" }
|
23
24
|
let(:mock_response) do
|
24
25
|
OpenStruct.new(
|
25
|
-
output:
|
26
|
-
|
27
|
-
|
28
|
-
OpenStruct.new(
|
29
|
-
|
26
|
+
output:
|
27
|
+
OpenStruct.new(
|
28
|
+
message:
|
29
|
+
OpenStruct.new(
|
30
|
+
content: [OpenStruct.new(text: '{"sentiment": "positive"}')]
|
31
|
+
)
|
30
32
|
)
|
31
|
-
)
|
32
33
|
)
|
33
34
|
end
|
34
35
|
|
35
36
|
before do
|
36
|
-
allow_any_instance_of(Aws::BedrockRuntime::Client).to receive(
|
37
|
+
allow_any_instance_of(Aws::BedrockRuntime::Client).to receive(
|
38
|
+
:converse
|
39
|
+
).and_return(mock_response)
|
37
40
|
end
|
38
41
|
|
39
|
-
it
|
42
|
+
it "returns a hash with raw and parsed response" do
|
40
43
|
response = provider.call(prompt, task)
|
41
|
-
expect(response).to eq(
|
42
|
-
|
43
|
-
|
44
|
-
|
44
|
+
expect(response).to eq(
|
45
|
+
{
|
46
|
+
raw: '{"sentiment": "positive"}',
|
47
|
+
parsed: {
|
48
|
+
"sentiment" => "positive"
|
49
|
+
}
|
50
|
+
}
|
51
|
+
)
|
45
52
|
end
|
46
53
|
|
47
|
-
context
|
54
|
+
context "when the response is not valid JSON" do
|
48
55
|
let(:mock_response) do
|
49
56
|
OpenStruct.new(
|
50
|
-
output:
|
51
|
-
|
52
|
-
|
53
|
-
OpenStruct.new(
|
54
|
-
|
57
|
+
output:
|
58
|
+
OpenStruct.new(
|
59
|
+
message:
|
60
|
+
OpenStruct.new(
|
61
|
+
content: [OpenStruct.new(text: "not valid json")]
|
62
|
+
)
|
55
63
|
)
|
56
|
-
)
|
57
64
|
)
|
58
65
|
end
|
59
66
|
|
60
|
-
it
|
67
|
+
it "returns raw response with nil parsed value" do
|
61
68
|
response = provider.call(prompt, task)
|
62
|
-
expect(response).to eq({
|
63
|
-
raw: 'not valid json',
|
64
|
-
parsed: nil
|
65
|
-
})
|
69
|
+
expect(response).to eq({ raw: "not valid json", parsed: nil })
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
69
|
-
context
|
73
|
+
context "when the response content is nil" do
|
70
74
|
let(:mock_response) do
|
71
75
|
OpenStruct.new(
|
72
|
-
output: OpenStruct.new(
|
73
|
-
message: OpenStruct.new(
|
74
|
-
content: nil
|
75
|
-
)
|
76
|
-
)
|
76
|
+
output: OpenStruct.new(message: OpenStruct.new(content: nil))
|
77
77
|
)
|
78
78
|
end
|
79
79
|
|
80
|
-
it
|
80
|
+
it "returns nil for both raw and parsed values" do
|
81
81
|
response = provider.call(prompt, task)
|
82
|
-
expect(response).to eq({
|
83
|
-
raw: nil,
|
84
|
-
parsed: nil
|
85
|
-
})
|
82
|
+
expect(response).to eq({ raw: nil, parsed: nil })
|
86
83
|
end
|
87
84
|
end
|
88
85
|
|
89
|
-
context
|
86
|
+
context "when the response content array is empty" do
|
90
87
|
let(:mock_response) do
|
91
88
|
OpenStruct.new(
|
92
|
-
output: OpenStruct.new(
|
93
|
-
message: OpenStruct.new(
|
94
|
-
content: []
|
95
|
-
)
|
96
|
-
)
|
89
|
+
output: OpenStruct.new(message: OpenStruct.new(content: []))
|
97
90
|
)
|
98
91
|
end
|
99
92
|
|
100
|
-
it
|
93
|
+
it "returns nil for both raw and parsed values" do
|
101
94
|
response = provider.call(prompt, task)
|
102
|
-
expect(response).to eq({
|
103
|
-
raw: nil,
|
104
|
-
parsed: nil
|
105
|
-
})
|
95
|
+
expect(response).to eq({ raw: nil, parsed: nil })
|
106
96
|
end
|
107
97
|
end
|
108
98
|
end
|
109
|
-
end
|
99
|
+
end
|
@@ -1,62 +1,54 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
2
3
|
|
3
4
|
RSpec.describe Lluminary::Providers::OpenAI do
|
4
|
-
let(:config) { { api_key:
|
5
|
+
let(:config) { { api_key: "test-key" } }
|
5
6
|
let(:provider) { described_class.new(**config) }
|
6
7
|
|
7
|
-
describe
|
8
|
-
it
|
8
|
+
describe "#client" do
|
9
|
+
it "returns the OpenAI client instance" do
|
9
10
|
expect(provider.client).to be_a(OpenAI::Client)
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
|
-
describe
|
14
|
-
let(:prompt) {
|
15
|
-
let(:task) {
|
14
|
+
describe "#call" do
|
15
|
+
let(:prompt) { "Test prompt" }
|
16
|
+
let(:task) { "Test task" }
|
16
17
|
let(:mock_response) do
|
17
18
|
{
|
18
|
-
|
19
|
-
{
|
20
|
-
'message' => {
|
21
|
-
'content' => '{"summary": "Test response"}'
|
22
|
-
}
|
23
|
-
}
|
19
|
+
"choices" => [
|
20
|
+
{ "message" => { "content" => '{"summary": "Test response"}' } }
|
24
21
|
]
|
25
22
|
}
|
26
23
|
end
|
27
24
|
|
28
25
|
before do
|
29
|
-
allow_any_instance_of(OpenAI::Client).to receive(:chat).and_return(
|
26
|
+
allow_any_instance_of(OpenAI::Client).to receive(:chat).and_return(
|
27
|
+
mock_response
|
28
|
+
)
|
30
29
|
end
|
31
30
|
|
32
|
-
it
|
31
|
+
it "returns a hash with raw and parsed response" do
|
33
32
|
response = provider.call(prompt, task)
|
34
|
-
expect(response).to eq(
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
expect(response).to eq(
|
34
|
+
{
|
35
|
+
raw: '{"summary": "Test response"}',
|
36
|
+
parsed: {
|
37
|
+
"summary" => "Test response"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
)
|
38
41
|
end
|
39
42
|
|
40
|
-
context
|
43
|
+
context "when the response is not valid JSON" do
|
41
44
|
let(:mock_response) do
|
42
|
-
{
|
43
|
-
'choices' => [
|
44
|
-
{
|
45
|
-
'message' => {
|
46
|
-
'content' => 'not valid json'
|
47
|
-
}
|
48
|
-
}
|
49
|
-
]
|
50
|
-
}
|
45
|
+
{ "choices" => [{ "message" => { "content" => "not valid json" } }] }
|
51
46
|
end
|
52
47
|
|
53
|
-
it
|
48
|
+
it "returns raw response with nil parsed value" do
|
54
49
|
response = provider.call(prompt, task)
|
55
|
-
expect(response).to eq({
|
56
|
-
raw: 'not valid json',
|
57
|
-
parsed: nil
|
58
|
-
})
|
50
|
+
expect(response).to eq({ raw: "not valid json", parsed: nil })
|
59
51
|
end
|
60
52
|
end
|
61
53
|
end
|
62
|
-
end
|
54
|
+
end
|
@@ -1,14 +1,17 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
require "lluminary/providers/test"
|
3
4
|
|
4
5
|
RSpec.describe Lluminary::Providers::Test do
|
5
6
|
let(:provider) { described_class.new }
|
6
7
|
let(:prompt) { "Test prompt" }
|
7
|
-
let(:task_class)
|
8
|
+
let(:task_class) do
|
9
|
+
double("TaskClass", output_fields: { summary: { type: :string } })
|
10
|
+
end
|
8
11
|
let(:task) { double("Task", class: task_class) }
|
9
12
|
|
10
|
-
describe
|
11
|
-
it
|
13
|
+
describe "#call" do
|
14
|
+
it "returns a hash with raw and parsed response" do
|
12
15
|
response = provider.call(prompt, task)
|
13
16
|
|
14
17
|
expect(response).to be_a(Hash)
|
@@ -16,7 +19,7 @@ RSpec.describe Lluminary::Providers::Test do
|
|
16
19
|
expect(response[:parsed]).to eq({ "summary" => "Test string value" })
|
17
20
|
end
|
18
21
|
|
19
|
-
it
|
22
|
+
it "handles prompts with schema descriptions" do
|
20
23
|
prompt_with_schema = <<~PROMPT
|
21
24
|
Test prompt
|
22
25
|
|
@@ -36,22 +39,24 @@ RSpec.describe Lluminary::Providers::Test do
|
|
36
39
|
expect(response[:parsed]).to eq({ "summary" => "Test string value" })
|
37
40
|
end
|
38
41
|
|
39
|
-
it
|
40
|
-
task_class =
|
42
|
+
it "generates integer values for integer fields" do
|
43
|
+
task_class =
|
44
|
+
double("TaskClass", output_fields: { count: { type: :integer } })
|
41
45
|
task = double("Task", class: task_class)
|
42
|
-
|
46
|
+
|
43
47
|
response = provider.call(prompt, task)
|
44
48
|
expect(response[:raw]).to eq('{"count": 0}')
|
45
49
|
expect(response[:parsed]).to eq({ "count" => 0 })
|
46
50
|
end
|
47
51
|
|
48
|
-
it
|
49
|
-
task_class =
|
52
|
+
it "raises error for unsupported types" do
|
53
|
+
task_class =
|
54
|
+
double("TaskClass", output_fields: { value: { type: :unsupported } })
|
50
55
|
task = double("Task", class: task_class)
|
51
|
-
|
52
|
-
expect {
|
53
|
-
|
54
|
-
|
56
|
+
|
57
|
+
expect { provider.call(prompt, task) }.to raise_error(
|
58
|
+
"Unsupported type: unsupported"
|
59
|
+
)
|
55
60
|
end
|
56
61
|
end
|
57
|
-
end
|
62
|
+
end
|
@@ -1,31 +1,38 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "lluminary"
|
2
3
|
|
3
4
|
RSpec.describe Lluminary::Result do
|
4
5
|
let(:raw_response) { "Test response" }
|
5
6
|
let(:output) { { summary: "Test summary" } }
|
6
7
|
let(:prompt) { "Test prompt" }
|
7
|
-
let(:result)
|
8
|
+
let(:result) do
|
9
|
+
described_class.new(
|
10
|
+
raw_response: raw_response,
|
11
|
+
output: output,
|
12
|
+
prompt: prompt
|
13
|
+
)
|
14
|
+
end
|
8
15
|
|
9
|
-
describe
|
10
|
-
it
|
16
|
+
describe "#raw_response" do
|
17
|
+
it "returns the raw response" do
|
11
18
|
expect(result.raw_response).to eq(raw_response)
|
12
19
|
end
|
13
20
|
end
|
14
21
|
|
15
|
-
describe
|
16
|
-
it
|
22
|
+
describe "#output" do
|
23
|
+
it "returns an OpenStruct with the output data" do
|
17
24
|
expect(result.output).to be_a(OpenStruct)
|
18
25
|
expect(result.output.summary).to eq(output[:summary])
|
19
26
|
end
|
20
27
|
|
21
|
-
it
|
28
|
+
it "allows accessing output fields as methods" do
|
22
29
|
expect(result.output.summary).to eq(output[:summary])
|
23
30
|
end
|
24
31
|
end
|
25
32
|
|
26
|
-
describe
|
27
|
-
it
|
33
|
+
describe "#prompt" do
|
34
|
+
it "returns the prompt" do
|
28
35
|
expect(result.prompt).to eq(prompt)
|
29
36
|
end
|
30
37
|
end
|
31
|
-
end
|
38
|
+
end
|
@@ -1,11 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
2
3
|
|
3
4
|
RSpec.describe Lluminary::SchemaModel do
|
4
|
-
describe
|
5
|
+
describe ".build" do
|
5
6
|
let(:fields) do
|
6
7
|
{
|
7
|
-
name: {
|
8
|
-
|
8
|
+
name: {
|
9
|
+
type: :string,
|
10
|
+
description: "The user's name"
|
11
|
+
},
|
12
|
+
age: {
|
13
|
+
type: :integer,
|
14
|
+
description: "The user's age"
|
15
|
+
}
|
9
16
|
}
|
10
17
|
end
|
11
18
|
|
@@ -16,35 +23,39 @@ RSpec.describe Lluminary::SchemaModel do
|
|
16
23
|
]
|
17
24
|
end
|
18
25
|
|
19
|
-
let(:model_class)
|
26
|
+
let(:model_class) do
|
27
|
+
described_class.build(fields: fields, validations: validations)
|
28
|
+
end
|
20
29
|
|
21
|
-
it
|
30
|
+
it "creates a class that inherits from SchemaModel" do
|
22
31
|
expect(model_class.ancestors).to include(described_class)
|
23
32
|
end
|
24
33
|
|
25
|
-
it
|
34
|
+
it "includes ActiveModel::Validations" do
|
26
35
|
expect(model_class.ancestors).to include(ActiveModel::Validations)
|
27
36
|
end
|
28
37
|
|
29
|
-
it
|
38
|
+
it "adds accessors for fields" do
|
30
39
|
instance = model_class.new(name: "John", age: 30)
|
31
40
|
expect(instance.name).to eq("John")
|
32
41
|
expect(instance.age).to eq(30)
|
33
42
|
end
|
34
43
|
|
35
|
-
it
|
44
|
+
it "validates presence" do
|
36
45
|
instance = model_class.new(age: 30)
|
37
46
|
expect(instance.valid?).to be false
|
38
47
|
expect(instance.errors.full_messages).to include("Name can't be blank")
|
39
48
|
end
|
40
49
|
|
41
|
-
it
|
50
|
+
it "validates numericality" do
|
42
51
|
instance = model_class.new(name: "John", age: 0)
|
43
52
|
expect(instance.valid?).to be false
|
44
|
-
expect(instance.errors.full_messages).to include(
|
53
|
+
expect(instance.errors.full_messages).to include(
|
54
|
+
"Age must be greater than 0"
|
55
|
+
)
|
45
56
|
end
|
46
57
|
|
47
|
-
it
|
58
|
+
it "validates types" do
|
48
59
|
instance = model_class.new(name: 123, age: "30")
|
49
60
|
expect(instance.valid?).to be false
|
50
61
|
expect(instance.errors.full_messages).to include(
|
@@ -53,34 +64,32 @@ RSpec.describe Lluminary::SchemaModel do
|
|
53
64
|
)
|
54
65
|
end
|
55
66
|
|
56
|
-
it
|
57
|
-
fields = {
|
58
|
-
price: { type: :float, description: "The price" }
|
59
|
-
}
|
67
|
+
it "validates float types" do
|
68
|
+
fields = { price: { type: :float, description: "The price" } }
|
60
69
|
model_class = described_class.build(fields: fields, validations: [])
|
61
|
-
|
70
|
+
|
62
71
|
# Test that nil is allowed
|
63
72
|
instance = model_class.new(price: nil)
|
64
73
|
expect(instance.valid?).to be true
|
65
|
-
|
74
|
+
|
66
75
|
# Test invalid float value
|
67
76
|
instance = model_class.new(price: "not a float")
|
68
77
|
expect(instance.valid?).to be false
|
69
78
|
expect(instance.errors.full_messages).to include("Price must be a float")
|
70
|
-
|
79
|
+
|
71
80
|
# Test valid float value
|
72
81
|
instance = model_class.new(price: 12.34)
|
73
82
|
expect(instance.valid?).to be true
|
74
83
|
end
|
75
84
|
|
76
|
-
it
|
85
|
+
it "accepts valid attributes" do
|
77
86
|
instance = model_class.new(name: "John", age: 30)
|
78
87
|
expect(instance.valid?).to be true
|
79
88
|
end
|
80
89
|
|
81
|
-
it
|
90
|
+
it "provides access to raw attributes" do
|
82
91
|
instance = model_class.new(name: "John", age: 30)
|
83
92
|
expect(instance.attributes).to eq({ "name" => "John", "age" => 30 })
|
84
93
|
end
|
85
94
|
end
|
86
|
-
end
|
95
|
+
end
|