123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- require 'rails_helper'
- describe Agents::JavaScriptAgent do
- before do
- @valid_params = {
- name: "somename",
- options: {
- code: "Agent.check = function() { this.createEvent({ 'message': 'hi' }); };",
- }
- }
- @agent = Agents::JavaScriptAgent.new(@valid_params)
- @agent.user = users(:jane)
- @agent.save!
- end
- describe "validations" do
- it "requires 'code'" do
- expect(@agent).to be_valid
- @agent.options['code'] = ''
- expect(@agent).not_to be_valid
- @agent.options.delete('code')
- expect(@agent).not_to be_valid
- end
- it "checks for a valid 'language', but allows nil" do
- expect(@agent).to be_valid
- @agent.options['language'] = ''
- expect(@agent).to be_valid
- @agent.options.delete('language')
- expect(@agent).to be_valid
- @agent.options['language'] = 'foo'
- expect(@agent).not_to be_valid
- %w[javascript JavaScript coffeescript CoffeeScript].each do |valid_language|
- @agent.options['language'] = valid_language
- expect(@agent).to be_valid
- end
- end
- it "accepts a credential, but it must exist" do
- expect(@agent).to be_valid
- @agent.options['code'] = 'credential:foo'
- expect(@agent).not_to be_valid
- users(:jane).user_credentials.create! credential_name: "foo", credential_value: "bar"
- expect(@agent.reload).to be_valid
- end
- end
- describe "#working?" do
- describe "when expected_update_period_in_days is set" do
- it "returns false when more than expected_update_period_in_days have passed since the last event creation" do
- @agent.options['expected_update_period_in_days'] = 1
- @agent.save!
- expect(@agent).not_to be_working
- @agent.check
- expect(@agent.reload).to be_working
- three_days_from_now = 3.days.from_now
- allow(Time).to receive(:now) { three_days_from_now }
- expect(@agent).not_to be_working
- end
- end
- describe "when expected_receive_period_in_days is set" do
- it "returns false when more than expected_receive_period_in_days have passed since the last event was received" do
- @agent.options['expected_receive_period_in_days'] = 1
- @agent.save!
- expect(@agent).not_to be_working
- Agents::JavaScriptAgent.async_receive @agent.id, [events(:bob_website_agent_event).id]
- expect(@agent.reload).to be_working
- two_days_from_now = 2.days.from_now
- allow(Time).to receive(:now) { two_days_from_now }
- expect(@agent.reload).not_to be_working
- end
- end
- end
- describe "executing code" do
- it "works by default" do
- @agent.options = @agent.default_options
- @agent.options['make_event'] = true
- @agent.save!
- expect {
- expect {
- @agent.receive([events(:bob_website_agent_event)])
- @agent.check
- }.not_to(change { AgentLog.count })
- }.to change { Event.count }.by(2)
- end
- describe "using credentials as code" do
- before do
- @agent.user.user_credentials.create credential_name: 'code-foo',
- credential_value: 'Agent.check = function() { this.log("ran it"); };'
- @agent.options['code'] = "credential:code-foo\n\n"
- @agent.save!
- end
- it "accepts credentials" do
- @agent.check
- expect(AgentLog.last.message).to eq("ran it")
- end
- it "logs an error when the credential goes away" do
- @agent.user.user_credentials.delete_all
- @agent.reload.check
- expect(AgentLog.last.message).to eq("Unable to find credential")
- end
- end
- describe "error handling" do
- it "should log an error when V8 has issues" do
- @agent.options['code'] = 'syntax error!'
- @agent.save!
- expect {
- expect {
- @agent.check
- }.not_to raise_error
- }.to change { AgentLog.count }.by(1)
- expect(AgentLog.last.message).to match(/Unexpected identifier/)
- expect(AgentLog.last.level).to eq(4)
- end
- it "should log an error when JavaScript throws" do
- @agent.options['code'] = 'Agent.check = function() { throw "oh no"; };'
- @agent.save!
- expect {
- expect {
- @agent.check
- }.not_to raise_error
- }.to change { AgentLog.count }.by(1)
- expect(AgentLog.last.message).to match(/oh no/)
- expect(AgentLog.last.level).to eq(4)
- end
- end
- describe "getMemory" do
- it "won't store NaNs" do
- @agent.options['code'] = 'Agent.check = function() { this.memory("foo", NaN); };'
- @agent.save!
- @agent.check
- expect(@agent.memory['foo']).to eq('NaN') # string
- @agent.save!
- expect { @agent.reload.memory }.not_to raise_error
- end
- it "it stores an Array" do
- @agent.options['code'] = 'Agent.check = function() {
- var arr = [1,2];
- this.memory("foo", arr);
- };'
- @agent.save!
- @agent.check
- expect(@agent.memory['foo']).to eq([1, 2])
- @agent.save!
- expect { @agent.reload.memory }.not_to raise_error
- end
- it "it stores a Hash" do
- @agent.options['code'] = 'Agent.check = function() {
- var obj = {};
- obj["one"] = 1;
- obj["two"] = [1,2];
- this.memory("foo", obj);
- };'
- @agent.save!
- @agent.check
- expect(@agent.memory['foo']).to eq({ "one" => 1, "two" => [1, 2] })
- @agent.save!
- expect { @agent.reload.memory }.not_to raise_error
- end
- it "it stores a nested Hash" do
- @agent.options['code'] = 'Agent.check = function() {
- var u = {};
- u["one"] = 1;
- u["two"] = 2;
- var obj = {};
- obj["three"] = 3;
- obj["four"] = u;
- this.memory("foo", obj);
- };'
- @agent.save!
- @agent.check
- expect(@agent.memory['foo']).to eq({ "three" => 3, "four" => { "one" => 1, "two" => 2 } })
- @agent.save!
- expect { @agent.reload.memory }.not_to raise_error
- end
- it "it stores null" do
- @agent.options['code'] = 'Agent.check = function() {
- this.memory("foo", "test");
- this.memory("foo", null);
- };'
- @agent.save!
- @agent.check
- expect(@agent.memory['foo']).to eq(nil)
- @agent.save!
- expect { @agent.reload.memory }.not_to raise_error
- end
- it "it stores false" do
- @agent.options['code'] = 'Agent.check = function() {
- this.memory("foo", "test");
- this.memory("foo", false);
- };'
- @agent.save!
- @agent.check
- expect(@agent.memory['foo']).to eq(false)
- @agent.save!
- expect { @agent.reload.memory }.not_to raise_error
- end
- end
- describe "setMemory" do
- it "stores an object" do
- @agent.options['code'] = 'Agent.check = function() {
- var u = {};
- u["one"] = 1;
- u["two"] = 2;
- this.setMemory(u);
- };'
- @agent.save!
- @agent.check
- expect(@agent.memory).to eq({ "one" => 1, "two" => 2 })
- @agent.save!
- expect { @agent.reload.memory }.not_to raise_error
- end
- end
- describe "deleteKey" do
- it "deletes a memory key" do
- @agent.memory = { foo: "baz" }
- @agent.options['code'] = 'Agent.check = function() {
- this.deleteKey("foo");
- };'
- @agent.save!
- @agent.check
- expect(@agent.memory['foo']).to be_nil
- expect { @agent.reload.memory }.not_to raise_error
- end
- it "returns the string value of the deleted key" do
- @agent.memory = { foo: "baz" }
- @agent.options['code'] = 'Agent.check = function() {
- this.createEvent({ message: this.deleteKey("foo")});
- };'
- @agent.save!
- @agent.check
- created_event = @agent.events.last
- expect(created_event.payload).to eq('message' => "baz")
- end
- it "returns the hash value of the deleted key" do
- @agent.memory = { foo: { baz: 'test' } }
- @agent.options['code'] = 'Agent.check = function() {
- this.createEvent({ message: this.deleteKey("foo")});
- };'
- @agent.save!
- @agent.check
- created_event = @agent.events.last
- expect(created_event.payload).to eq('message' => { 'baz' => 'test' })
- end
- end
- describe "creating events" do
- it "creates events with this.createEvent in the JavaScript environment" do
- @agent.options['code'] =
- 'Agent.check = function() { this.createEvent({ message: "This is an event!", stuff: { foo: 5 } }); };'
- @agent.save!
- expect {
- expect {
- @agent.check
- }.not_to(change { AgentLog.count })
- }.to change { Event.count }.by(1)
- created_event = @agent.events.last
- expect(created_event.payload).to eq({ 'message' => "This is an event!", 'stuff' => { 'foo' => 5 } })
- end
- end
- describe "logging" do
- it "can output AgentLogs with this.log and this.error in the JavaScript environment" do
- @agent.options['code'] = 'Agent.check = function() { this.log("woah"); this.error("WOAH!"); };'
- @agent.save!
- expect {
- expect {
- @agent.check
- }.not_to raise_error
- }.to change { AgentLog.count }.by(2)
- log1, log2 = AgentLog.last(2)
- expect(log1.message).to eq("woah")
- expect(log1.level).to eq(3)
- expect(log2.message).to eq("WOAH!")
- expect(log2.level).to eq(4)
- end
- end
- describe "escaping and unescaping HTML" do
- it "can escape and unescape html with this.escapeHtml and this.unescapeHtml in the javascript environment" do
- @agent.options['code'] =
- 'Agent.check = function() { this.createEvent({ escaped: this.escapeHtml(\'test \"escaping\" <characters>\'), unescaped: this.unescapeHtml(\'test "unescaping" <characters>\')}); };'
- @agent.save!
- expect {
- expect {
- @agent.check
- }.not_to(change { AgentLog.count })
- }.to change { Event.count }.by(1)
- created_event = @agent.events.last
- expect(created_event.payload).to eq({ 'escaped' => 'test "escaping" <characters>',
- 'unescaped' => 'test "unescaping" <characters>' })
- end
- end
- describe "getting incoming events" do
- it "can access incoming events in the JavaScript enviroment via this.incomingEvents" do
- event = Event.new
- event.agent = agents(:bob_rain_notifier_agent)
- event.payload = { data: "Something you should know about" }
- event.save!
- event.reload
- @agent.options['code'] = <<-JS
- Agent.receive = function() {
- var events = this.incomingEvents();
- for(var i = 0; i < events.length; i++) {
- this.createEvent({ 'message': 'I got an event!', 'event_was': events[i].payload });
- }
- }
- JS
- @agent.save!
- expect {
- expect {
- @agent.receive([events(:bob_website_agent_event), event])
- }.not_to(change { AgentLog.count })
- }.to change { Event.count }.by(2)
- created_event = @agent.events.first
- expect(created_event.payload).to eq({ 'message' => "I got an event!",
- 'event_was' => { 'data' => "Something you should know about" } })
- end
- end
- describe "getting and setting memory, getting options" do
- it "can access options via this.options and work with memory via this.memory" do
- @agent.options['code'] = <<-JS
- Agent.check = function() {
- if (this.options('make_event')) {
- var callCount = this.memory('callCount') || 0;
- this.memory('callCount', callCount + 1);
- }
- };
- JS
- @agent.save!
- expect {
- expect {
- @agent.check
- expect(@agent.memory['callCount']).not_to be_present
- @agent.options['make_event'] = true
- @agent.check
- expect(@agent.memory['callCount']).to eq(1)
- @agent.check
- expect(@agent.memory['callCount']).to eq(2)
- @agent.memory['callCount'] = 20
- @agent.check
- expect(@agent.memory['callCount']).to eq(21)
- }.not_to(change { AgentLog.count })
- }.not_to(change { Event.count })
- end
- end
- describe "using CoffeeScript" do
- it "will accept a 'language' of 'CoffeeScript'" do
- @agent.options['code'] = 'Agent.check = -> this.log("hello from coffeescript")'
- @agent.options['language'] = 'CoffeeScript'
- @agent.save!
- expect {
- @agent.check
- }.not_to raise_error
- expect(AgentLog.last.message).to eq("hello from coffeescript")
- end
- end
- describe "user credentials" do
- it "can access an existing credential" do
- @agent.send(:set_credential, 'test', 'hello')
- @agent.options['code'] = 'Agent.check = function() { this.log(this.credential("test")); };'
- @agent.save!
- @agent.check
- expect(AgentLog.last.message).to eq("hello")
- end
- it "will create a new credential" do
- @agent.options['code'] = 'Agent.check = function() { this.credential("test","1234"); };'
- @agent.save!
- expect {
- @agent.check
- }.to change(UserCredential, :count).by(1)
- end
- it "updates an existing credential" do
- @agent.send(:set_credential, 'test', 1234)
- @agent.options['code'] = 'Agent.check = function() { this.credential("test","12345"); };'
- @agent.save!
- expect {
- @agent.check
- }.to change(UserCredential, :count).by(0)
- expect(@agent.user.user_credentials.last.credential_value).to eq('12345')
- end
- end
- end
- describe "KVS" do
- before do
- Agents::KeyValueStoreAgent.create!(
- name: "kvs1",
- options: {
- key: "{{ key }}",
- value: "{{ value }}",
- variable: "var1",
- },
- memory: {
- x: "Huginn",
- },
- user: users(:jane),
- control_targets: [@agent]
- )
- Agents::KeyValueStoreAgent.create!(
- name: "kvs2",
- options: {
- key: "{{ key }}",
- value: "{{ value }}",
- variable: "var2",
- },
- memory: {
- y: "Muninn",
- },
- user: users(:jane),
- control_targets: [@agent]
- )
- end
- it "can be accessed via Agent.kvs()" do
- @agent.options['code'] = <<~JS
- Agent.check = function() {
- this.createEvent({ message: `I got values from KVS: ${this.kvs.var1["x"]} and ${this.kvs.var2["y"]}.` });
- };
- JS
- @agent.save!
- expect {
- @agent.check
- }.to change { @agent.events.count }.by(1)
- created_event = @agent.events.last
- expect(created_event.payload).to eq("message" => "I got values from KVS: Huginn and Muninn.")
- end
- end
- end
|