Skip to content

Commit 2fc596d

Browse files
authored
feat(state)!: Adds state management component (#74)
* feat(state): Add State Management component * test(state): Adds State Management component specs
1 parent ff9dc78 commit 2fc596d

File tree

4 files changed

+155
-2
lines changed

4 files changed

+155
-2
lines changed

coverage/coverage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"timestamp":1728408645,"command_name":"Unit Tests","files":[{"filename":"/home/bougyman/rubyists/dapr/lib/dapr.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,1,null,null,1,1,5,null,null,null,1,1,1,1,1,null,null]},"covered_strength":1.4,"covered_lines":10,"lines_of_code":10},{"filename":"/home/bougyman/rubyists/dapr/lib/dapr/client.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,1,1,1,1,null,1,1,null,1,1,1,1,1,null,1,4,null,1,1,null,null,1,9,null,null,1,1,null,null,1,12,null,null,null,1,1,null,1,5,null,null,1,1,1,1,null,1,null,null,1,1,null,null,null,null,null]},"covered_strength":1.7878787878787878,"covered_lines":33,"lines_of_code":33},{"filename":"/home/bougyman/rubyists/dapr/lib/dapr/client/configuration.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,null,1,1,1,null,1,null,1,1,null,null,1,null,null,1,null,1,1,null,1,1,1,null,null,null,null,null,null,null,1,1,1,1,null,null,1,1,1,null,null,null,null,null]},"covered_strength":1.0,"covered_lines":21,"lines_of_code":21},{"filename":"/home/bougyman/rubyists/dapr/lib/dapr/client/lock.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,1,null,1,1,1,null,1,null,1,1,null,null,1,null,null,1,null,1,1,null,null,null,null,null,null,1,6,6,null,null,null,null,null,null,1,6,6,null,null,null,1,6,6,5,5,null,null,1,null,null,null,1,4,4,4,null,3,3,null,null,1,null,null,1,18,null,null,null,null,null,1,6,null,null,null,null,null]},"covered_strength":3.085714285714286,"covered_lines":35,"lines_of_code":35},{"filename":"/home/bougyman/rubyists/dapr/lib/dapr/version.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,1,null,1,null,null,null]},"covered_strength":1.0,"covered_lines":3,"lines_of_code":3}],"metrics":{"covered_percent":100.0,"covered_strength":2.0098039215686274,"covered_lines":102,"total_lines":102}}
1+
{"timestamp":1729718516,"command_name":"Unit Tests","files":[{"filename":"/home/bougyman/rubyists/dapr/lib/dapr.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,1,null,null,1,1,7,null,null,null,1,1,1,1,1,null,null]},"covered_strength":1.6,"covered_lines":10,"lines_of_code":10},{"filename":"/home/bougyman/rubyists/dapr/lib/dapr/client.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,1,1,1,1,null,1,1,null,1,1,1,1,1,1,null,1,4,null,1,1,null,null,1,11,null,null,1,1,null,null,1,14,null,null,null,1,1,null,1,5,null,null,1,1,1,1,null,1,null,null,1,1,null,null,null,null,null]},"covered_strength":1.8823529411764706,"covered_lines":34,"lines_of_code":34},{"filename":"/home/bougyman/rubyists/dapr/lib/dapr/client/configuration.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,null,1,1,1,null,1,null,1,1,null,null,1,null,null,1,null,1,1,null,1,1,1,null,null,null,null,null,null,null,1,1,1,1,null,null,1,1,1,null,null,null,null,null]},"covered_strength":1.0,"covered_lines":21,"lines_of_code":21},{"filename":"/home/bougyman/rubyists/dapr/lib/dapr/client/lock.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,1,null,1,1,1,null,1,null,1,1,null,null,1,null,null,1,null,1,1,null,null,null,null,null,null,1,6,6,null,null,null,null,null,null,1,6,6,null,null,null,1,6,6,5,5,null,null,1,null,null,null,1,4,4,4,null,3,3,null,null,1,null,null,1,18,null,null,null,null,null,1,6,null,null,null,null,null]},"covered_strength":3.085714285714286,"covered_lines":35,"lines_of_code":35},{"filename":"/home/bougyman/rubyists/dapr/lib/dapr/client/state.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,null,1,1,1,null,null,1,null,1,1,null,1,null,1,1,1,1,1,1,null,null,null,null,null,null,1,1,null,null,null,null,null,null,null,1,1,null,null,null,null,null,1,2,null,null,null,null,null,null,1,1,1,1,null,null,null,null,1,3,1,1,1,null,null,null,null,null]},"covered_strength":1.103448275862069,"covered_lines":29,"lines_of_code":29},{"filename":"/home/bougyman/rubyists/dapr/lib/dapr/version.rb","covered_percent":100.0,"coverage":{"lines":[null,null,1,1,null,1,null,null,null]},"covered_strength":1.0,"covered_lines":3,"lines_of_code":3}],"metrics":{"covered_percent":100.0,"covered_strength":1.8484848484848484,"covered_lines":132,"total_lines":132}}

lib/dapr/client.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ module Dapr
1111
# The namespace for the Dapr client
1212
module Client
1313
include SemanticLogger::Loggable
14+
Runtime = ::Dapr::Proto::Runtime::V1
1415
DAPR_PORT = ENV.fetch('DAPR_GRPC_PORT', nil)
1516
DAPR_URI = ENV.fetch('DAPR_GRPC_HOST', 'localhost')
16-
DAPR_STUB = ::Dapr::Proto::Runtime::V1::Dapr::Stub
17+
DAPR_STUB = Runtime::Dapr::Stub
1718

1819
def self.client(dapr_port: DAPR_PORT, dapr_uri: DAPR_URI)
1920
return DummyClient.new if dapr_port.nil?

lib/dapr/client/state.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../client'
4+
5+
module Rubyists
6+
module Dapr
7+
module Client
8+
# The State class is a client for the Dapr State Management Building block
9+
# Info here: https://docs.dapr.io/developing-applications/building-blocks/state-management/
10+
class State
11+
# Include the client module
12+
include Client
13+
include SemanticLogger::Loggable
14+
15+
attr_reader :store_name
16+
17+
Empty = Google::Protobuf::Empty
18+
Runtime = Client::Runtime
19+
GetBulkStateResponse = Runtime::GetBulkStateResponse
20+
GetStateRequest = Runtime::GetBulkStateRequest
21+
SaveStateRequest = Runtime::SaveStateRequest
22+
DEFAULT_STORE_NAME = 'statestore'
23+
24+
# @param keys [Array<String>] keys to retrieve from the state store
25+
# @param store_name [String] name of the State Management component, defaults to 'dapr-statestore'
26+
# @param metadata [Hash] metadata to include
27+
#
28+
# @return [GetBulkStateResponse] The response from the Dapr State Management component
29+
def self.get(*keys, store_name: DEFAULT_STORE_NAME, metadata: {})
30+
new(store_name).get(keys, metadata:)
31+
end
32+
33+
# @param states [Hash] states to set in the state store, key/value pairs
34+
# @param store_name [String] name of the State Management component, defaults to 'dapr-statestore'
35+
# @param metadata [Hash] metadata to include
36+
#
37+
# @return [Empty] The response from the Dapr State Management component
38+
def self.set(states, store_name: DEFAULT_STORE_NAME, metadata: {})
39+
new(store_name).set(states, metadata:)
40+
end
41+
42+
# Initialize the State Management client
43+
#
44+
# @param store_name [String] name of the Dapr Configuration component to use
45+
def initialize(store_name)
46+
@store_name = store_name
47+
end
48+
49+
# @param keys [Array<String>] keys to retrieve from the state store
50+
# @param metadata [Hash] metadata to include
51+
#
52+
# @return [Array<BulkStateItem>] Array of items returned by the state store
53+
def get(keys, metadata: {})
54+
logger.debug('Getting state', keys:, store_name:, metadata:)
55+
response = singleton.get_bulk_state GetStateRequest.new(store_name:, keys:, metadata:)
56+
response.items
57+
end
58+
59+
# @param states [Hash] states to set (key/values in the state store)
60+
# @param metadata [Hash] metadata to include
61+
def set(states, metadata: {})
62+
states = states.map { |key, value| { key:, value:, metadata: } }
63+
request = SaveStateRequest.new(store_name:, states:)
64+
logger.debug('Setting state', states:, store_name:)
65+
singleton.save_state request
66+
end
67+
end
68+
end
69+
end
70+
end

test/dapr/client/test_state.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../test_helper'
4+
5+
# Tests for the Dapr State Management component
6+
class TestDaprState < Minitest::Test
7+
require Rubyists::Dapr::LIBROOT / :client / :state
8+
DummyClient = Rubyists::Dapr::Client::DummyClient
9+
Runtime = ::Dapr::Proto::Runtime::V1
10+
Common = ::Dapr::Proto::Common::V1
11+
Empty = Google::Protobuf::Empty
12+
13+
def setup_dummy_client!
14+
DummyClient.define_method(:states) { @states ||= {} }
15+
define_get_bulk_state_method!
16+
define_set_bulk_state_method!
17+
end
18+
19+
def define_get_bulk_state_method!
20+
DummyClient.define_method(:get_bulk_state) do |*args, &_block|
21+
items = args.shift.keys.map.with_index do |key, index|
22+
metadata = { key: "METAKEY-#{index}", value: "METAVALUE-#{index}" }
23+
data = "test-state-data-#{index}"
24+
Runtime::BulkStateItem.new(key:, data:, etag: '1', error: nil, metadata:)
25+
end
26+
Runtime::GetBulkStateResponse.new(items:)
27+
end
28+
end
29+
30+
def define_set_bulk_state_method!
31+
DummyClient.define_method(:save_state) do |*args, &_block|
32+
args.shift.states.map { |s| states[s.key] = s.value }
33+
Empty
34+
end
35+
end
36+
37+
def make_it_testy!
38+
return if testy?
39+
40+
setup_dummy_client!
41+
@testy = true
42+
end
43+
44+
def testy?
45+
@testy
46+
end
47+
48+
context 'State' do # rubocop:disable Metrics/BlockLength
49+
should 'Read existing State item' do
50+
make_it_testy!
51+
messages = semantic_logger_events(Rubyists::Dapr::Client::State) do
52+
states = Rubyists::Dapr::Client::State.get('test_item1', 'test_item2')
53+
54+
assert_equal 2, states.size
55+
assert_equal 'test-state-data-0', states[0].data
56+
assert_equal 'test_item1', states[0].key
57+
assert_equal 'test_item2', states[1].key
58+
assert_equal 'test-state-data-1', states[1].data
59+
end
60+
61+
assert_equal 1, messages.size
62+
assert_semantic_logger_event messages.first, level: :debug, message: 'Getting state'
63+
refute_nil messages.first.payload[:keys]
64+
end
65+
66+
should 'Set a State item' do
67+
make_it_testy!
68+
messages = semantic_logger_events(Rubyists::Dapr::Client::State) do
69+
items = { 'test_item1' => 'test-state-data-writes-0', 'test_item2' => 'test-state-data-writes-1' }
70+
response = Rubyists::Dapr::Client::State.set(items)
71+
72+
assert_equal Empty, response
73+
end
74+
75+
assert_equal 1, messages.size
76+
assert_semantic_logger_event messages.first, level: :debug, message: 'Setting state'
77+
assert_equal [{ key: 'test_item1', value: 'test-state-data-writes-0', metadata: {} },
78+
{ key: 'test_item2', value: 'test-state-data-writes-1', metadata: {} }],
79+
messages.first.payload[:states]
80+
end
81+
end
82+
end

0 commit comments

Comments
 (0)