evernote_agent_spec.rb 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. require 'rails_helper'
  2. describe Agents::EvernoteAgent do
  3. class FakeEvernoteNoteStore
  4. attr_accessor :notes, :tags, :notebooks
  5. def initialize
  6. @notes, @tags, @notebooks = [], [], []
  7. end
  8. def createNote(note)
  9. note.attributes = OpenStruct.new(source: nil, sourceURL: nil)
  10. note.guid = @notes.length + 1
  11. @notes << note
  12. note
  13. end
  14. def updateNote(note)
  15. note.attributes = OpenStruct.new(source: nil, sourceURL: nil)
  16. old_note = @notes.find {|en_note| en_note.guid == note.guid}
  17. @notes[@notes.index(old_note)] = note
  18. note
  19. end
  20. def getNote(guid, *other_args)
  21. @notes.find {|note| note.guid == guid}
  22. end
  23. def createNotebook(notebook)
  24. notebook.guid = @notebooks.length + 1
  25. @notebooks << notebook
  26. notebook
  27. end
  28. def createTag(tag)
  29. tag.guid = @tags.length + 1
  30. @tags << tag
  31. tag
  32. end
  33. def listNotebooks; @notebooks; end
  34. def listTags; @tags; end
  35. def getNoteTagNames(guid)
  36. getNote(guid).try(:tagNames) || []
  37. end
  38. def findNotesMetadata(*args); end
  39. end
  40. let(:en_note_store) do
  41. FakeEvernoteNoteStore.new
  42. end
  43. before do
  44. allow_any_instance_of(Agents::EvernoteAgent).to receive(:evernote_note_store) { en_note_store }
  45. end
  46. describe "#receive" do
  47. context "when mode is set to 'update'" do
  48. before do
  49. @options = {
  50. :mode => "update",
  51. :include_xhtml_content => "false",
  52. :expected_update_period_in_days => "2",
  53. :note => {
  54. :title => "{{title}}",
  55. :content => "{{content}}",
  56. :notebook => "{{notebook}}",
  57. :tagNames => "{{tag1}}, {{tag2}}"
  58. }
  59. }
  60. @agent = Agents::EvernoteAgent.new(:name => "evernote updater", :options => @options)
  61. @agent.service = services(:generic)
  62. @agent.user = users(:bob)
  63. @agent.save!
  64. @event = Event.new
  65. @event.agent = agents(:bob_website_agent)
  66. @event.payload = { :title => "xkcd Survey",
  67. :content => "The xkcd Survey: Big Data for a Big Planet",
  68. :notebook => "xkcd",
  69. :tag1 => "funny",
  70. :tag2 => "data" }
  71. @event.save!
  72. tag1 = OpenStruct.new(name: "funny")
  73. tag2 = OpenStruct.new(name: "data")
  74. [tag1, tag2].each { |tag| en_note_store.createTag(tag) }
  75. end
  76. it "adds a note for any payload it receives" do
  77. allow(en_note_store).to receive(:findNotesMetadata) { OpenStruct.new(notes: []) }
  78. Agents::EvernoteAgent.async_receive(@agent.id, [@event.id])
  79. expect(en_note_store.notes.size).to eq(1)
  80. expect(en_note_store.notes.first.title).to eq("xkcd Survey")
  81. expect(en_note_store.notebooks.size).to eq(1)
  82. expect(en_note_store.tags.size).to eq(2)
  83. expect(@agent.events.count).to eq(1)
  84. expect(@agent.events.first.payload).to eq({
  85. "title" => "xkcd Survey",
  86. "notebook" => "xkcd",
  87. "tags" => ["funny", "data"],
  88. "source" => nil,
  89. "source_url" => nil
  90. })
  91. end
  92. context "a note with the same title and notebook exists" do
  93. before do
  94. note1 = OpenStruct.new(title: "xkcd Survey", notebookGuid: 1)
  95. note2 = OpenStruct.new(title: "Footprints", notebookGuid: 1)
  96. [note1, note2].each { |note| en_note_store.createNote(note) }
  97. en_note_store.createNotebook(OpenStruct.new(name: "xkcd"))
  98. allow(en_note_store).to receive(:findNotesMetadata) {
  99. OpenStruct.new(notes: [note1]) }
  100. end
  101. it "updates the existing note" do
  102. Agents::EvernoteAgent.async_receive(@agent.id, [@event.id])
  103. expect(en_note_store.notes.size).to eq(2)
  104. expect(en_note_store.getNote(1).tagNames).to eq(["funny", "data"])
  105. expect(@agent.events.count).to eq(1)
  106. end
  107. end
  108. context "include_xhtml_content is set to 'true'" do
  109. before do
  110. @agent.options[:include_xhtml_content] = "true"
  111. @agent.save!
  112. end
  113. it "creates an event with note content wrapped in ENML" do
  114. allow(en_note_store).to receive(:findNotesMetadata) { OpenStruct.new(notes: []) }
  115. Agents::EvernoteAgent.async_receive(@agent.id, [@event.id])
  116. payload = @agent.events.first.payload
  117. expect(payload[:content]).to eq(
  118. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" \
  119. "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">" \
  120. "<en-note>The xkcd Survey: Big Data for a Big Planet</en-note>"
  121. )
  122. end
  123. end
  124. end
  125. end
  126. describe "#check" do
  127. context "when mode is set to 'read'" do
  128. before do
  129. @options = {
  130. :mode => "read",
  131. :include_xhtml_content => "false",
  132. :expected_update_period_in_days => "2",
  133. :note => {
  134. :title => "",
  135. :content => "",
  136. :notebook => "xkcd",
  137. :tagNames => "funny, comic"
  138. }
  139. }
  140. @checker = Agents::EvernoteAgent.new(:name => "evernote reader", :options => @options)
  141. @checker.service = services(:generic)
  142. @checker.user = users(:bob)
  143. @checker.schedule = "every_2h"
  144. @checker.save!
  145. @checker.created_at = 1.minute.ago
  146. en_note_store.createNote(
  147. OpenStruct.new(title: "xkcd Survey",
  148. notebookGuid: 1,
  149. updated: 2.minutes.ago.to_i * 1000,
  150. tagNames: ["funny", "comic"])
  151. )
  152. en_note_store.createNotebook(OpenStruct.new(name: "xkcd"))
  153. tag1 = OpenStruct.new(name: "funny")
  154. tag2 = OpenStruct.new(name: "comic")
  155. [tag1, tag2].each { |tag| en_note_store.createTag(tag) }
  156. allow(en_note_store).to receive(:findNotesMetadata) {
  157. notes = en_note_store.notes.select do |note|
  158. note.notebookGuid == 1 &&
  159. %w(funny comic).all? { |tag_name| note.tagNames.include?(tag_name) }
  160. end
  161. OpenStruct.new(notes: notes)
  162. }
  163. end
  164. context "the first time it checks" do
  165. it "returns only notes created/updated since it was created" do
  166. expect { @checker.check }.to change { Event.count }.by(0)
  167. end
  168. end
  169. context "on subsequent checks" do
  170. it "returns notes created/updated since the last time it checked" do
  171. expect { @checker.check }.to change { Event.count }.by(0)
  172. future_time = (Time.now + 1.minute).to_i * 1000
  173. en_note_store.createNote(
  174. OpenStruct.new(title: "Footprints",
  175. notebookGuid: 1,
  176. tagNames: ["funny", "comic", "recent"],
  177. updated: future_time))
  178. en_note_store.createNote(
  179. OpenStruct.new(title: "something else",
  180. notebookGuid: 2,
  181. tagNames: ["funny", "comic"],
  182. updated: future_time))
  183. expect { @checker.check }.to change { Event.count }.by(1)
  184. end
  185. it "returns notes tagged since the last time it checked" do
  186. en_note_store.createNote(
  187. OpenStruct.new(title: "Footprints",
  188. notebookGuid: 1,
  189. tagNames: [],
  190. created: Time.now.to_i * 1000,
  191. updated: Time.now.to_i * 1000))
  192. @checker.check
  193. en_note_store.getNote(2).tagNames = ["funny", "comic"]
  194. expect { @checker.check }.to change { Event.count }.by(1)
  195. end
  196. end
  197. end
  198. end
  199. describe "#validation" do
  200. before do
  201. @options = {
  202. :mode => "update",
  203. :include_xhtml_content => "false",
  204. :expected_update_period_in_days => "2",
  205. :note => {
  206. :title => "{{title}}",
  207. :content => "{{content}}",
  208. :notebook => "{{notebook}}",
  209. :tagNames => "{{tag1}}, {{tag2}}"
  210. }
  211. }
  212. @agent = Agents::EvernoteAgent.new(:name => "evernote updater", :options => @options)
  213. @agent.service = services(:generic)
  214. @agent.user = users(:bob)
  215. @agent.save!
  216. expect(@agent).to be_valid
  217. end
  218. it "requires the mode to be 'update' or 'read'" do
  219. @agent.options[:mode] = ""
  220. expect(@agent).not_to be_valid
  221. end
  222. context "mode is set to 'update'" do
  223. before do
  224. @agent.options[:mode] = "update"
  225. end
  226. it "requires some note parameter to be present" do
  227. @agent.options[:note].keys.each { |k| @agent.options[:note][k] = "" }
  228. expect(@agent).not_to be_valid
  229. end
  230. it "requires schedule to be 'never'" do
  231. @agent.schedule = 'never'
  232. expect(@agent).to be_valid
  233. @agent.schedule = 'every_1m'
  234. expect(@agent).not_to be_valid
  235. end
  236. end
  237. context "mode is set to 'read'" do
  238. before do
  239. @agent.options[:mode] = "read"
  240. end
  241. it "requires a schedule to be set" do
  242. @agent.schedule = 'every_1m'
  243. expect(@agent).to be_valid
  244. @agent.schedule = 'never'
  245. expect(@agent).not_to be_valid
  246. end
  247. end
  248. end
  249. # api wrapper classes
  250. describe Agents::EvernoteAgent::NoteStore do
  251. let(:note_store) { Agents::EvernoteAgent::NoteStore.new(en_note_store) }
  252. let(:note1) { OpenStruct.new(title: "first note") }
  253. let(:note2) { OpenStruct.new(title: "second note") }
  254. before do
  255. en_note_store.createNote(note1)
  256. en_note_store.createNote(note2)
  257. end
  258. describe "#create_note" do
  259. it "creates a note with given params in evernote note store" do
  260. note_store.create_note(title: "third note")
  261. expect(en_note_store.notes.size).to eq(3)
  262. expect(en_note_store.notes.last.title).to eq("third note")
  263. end
  264. it "returns a note" do
  265. expect(note_store.create_note(title: "third note")).to be_a(Agents::EvernoteAgent::Note)
  266. end
  267. end
  268. describe "#update_note" do
  269. it "updates an existing note with given params" do
  270. note_store.update_note(guid: 1, content: "some words")
  271. expect(en_note_store.notes.first.content).not_to be_nil
  272. expect(en_note_store.notes.size).to eq(2)
  273. end
  274. it "returns a note" do
  275. expect(note_store.update_note(guid: 1, content: "some words")).to be_a(Agents::EvernoteAgent::Note)
  276. end
  277. end
  278. describe "#find_note" do
  279. it "gets a note with the given guid" do
  280. note = note_store.find_note(2)
  281. expect(note.title).to eq("second note")
  282. expect(note).to be_a(Agents::EvernoteAgent::Note)
  283. end
  284. end
  285. describe "#find_tags" do
  286. let(:tag1) { OpenStruct.new(name: "tag1") }
  287. let(:tag2) { OpenStruct.new(name: "tag2") }
  288. let(:tag3) { OpenStruct.new(name: "tag3") }
  289. before do
  290. [tag1, tag2, tag3].each { |tag| en_note_store.createTag(tag) }
  291. end
  292. it "finds tags with the given guids" do
  293. expect(note_store.find_tags([1,3])).to eq([tag1, tag3])
  294. end
  295. end
  296. describe "#find_notebook" do
  297. let(:notebook1) { OpenStruct.new(name: "notebook1") }
  298. let(:notebook2) { OpenStruct.new(name: "notebook2") }
  299. before do
  300. [notebook1, notebook2].each {|notebook| en_note_store.createNotebook(notebook)}
  301. end
  302. it "finds a notebook with given name" do
  303. expect(note_store.find_notebook(name: "notebook1")).to eq(notebook1)
  304. expect(note_store.find_notebook(name: "notebook3")).to be_nil
  305. end
  306. it "finds a notebook with a given guid" do
  307. expect(note_store.find_notebook(guid: 2)).to eq(notebook2)
  308. expect(note_store.find_notebook(guid: 3)).to be_nil
  309. end
  310. end
  311. describe "#create_or_update_note" do
  312. let(:notebook1) { OpenStruct.new(name: "first notebook")}
  313. before do
  314. en_note_store.createNotebook(notebook1)
  315. end
  316. context "a note with given title and notebook does not exist" do
  317. before do
  318. allow(en_note_store).to receive(:findNotesMetadata) { OpenStruct.new(notes: []) }
  319. end
  320. it "creates a note" do
  321. result = note_store.create_or_update_note(title: "third note", notebook: "first notebook")
  322. expect(result).to be_a(Agents::EvernoteAgent::Note)
  323. expect(en_note_store.getNote(3)).to_not be_nil
  324. end
  325. it "also creates the notebook if it does not exist" do
  326. note_store.create_or_update_note(title: "third note", notebook: "second notebook")
  327. expect(note_store.find_notebook(name: "second notebook")).to_not be_nil
  328. end
  329. end
  330. context "such a note does exist" do
  331. let(:note) { OpenStruct.new(title: "a note", notebookGuid: 1) }
  332. before do
  333. en_note_store.createNote(note)
  334. allow(en_note_store).to receive(:findNotesMetadata) { OpenStruct.new(notes: [note]) }
  335. end
  336. it "updates the note" do
  337. prior_note_count = en_note_store.notes.size
  338. result = note_store.create_or_update_note(
  339. title: "a note", notebook: "first notebook", content: "test content")
  340. expect(result).to be_a(Agents::EvernoteAgent::Note)
  341. expect(en_note_store.notes.size).to eq(prior_note_count)
  342. expect(en_note_store.getNote(3).content).to include("test content")
  343. end
  344. end
  345. end
  346. end
  347. describe Agents::EvernoteAgent::NoteStore::Search do
  348. let(:note_store) { Agents::EvernoteAgent::NoteStore.new(en_note_store) }
  349. let(:note1) {
  350. OpenStruct.new(title: "first note", notebookGuid: 1, tagNames: ["funny", "comic"], updated: Time.now) }
  351. let(:note2) {
  352. OpenStruct.new(title: "second note", tagNames: ["funny", "comic"], updated: Time.now) }
  353. let(:note3) {
  354. OpenStruct.new(title: "third note", notebookGuid: 1, updated: Time.now - 2.minutes) }
  355. let(:search) do
  356. Agents::EvernoteAgent::NoteStore::Search.new(note_store,
  357. { tagNames: ["funny", "comic"], notebook: "xkcd" })
  358. end
  359. let(:search_with_time) do
  360. Agents::EvernoteAgent::NoteStore::Search.new(note_store,
  361. { notebook: "xkcd", last_checked_at: Time.now - 1.minute })
  362. end
  363. let(:search_with_time_and_tags) do
  364. Agents::EvernoteAgent::NoteStore::Search.new(note_store,
  365. { notebook: "xkcd", tagNames: ["funny", "comic"], notes_with_tags: [1], last_checked_at: Time.now - 1.minute })
  366. end
  367. before do
  368. en_note_store.createTag(OpenStruct.new(name: "funny"))
  369. en_note_store.createTag(OpenStruct.new(name: "comic"))
  370. en_note_store.createNotebook(OpenStruct.new(name: "xkcd"))
  371. [note1, note2, note3].each { |note| en_note_store.createNote(note) }
  372. end
  373. describe "#note_guids" do
  374. it "returns the guids of notes satisfying search options" do
  375. allow(en_note_store).to receive(:findNotesMetadata) { OpenStruct.new(notes: [note1]) }
  376. result = search.note_guids
  377. expect(result.size).to eq(1)
  378. expect(result.first).to eq(1)
  379. end
  380. end
  381. describe "#notes" do
  382. context "last_checked_at is not set" do
  383. it "returns notes satisfying the search options" do
  384. allow(en_note_store).to receive(:findNotesMetadata) { OpenStruct.new(notes: [note1]) }
  385. result = search.notes
  386. expect(result.size).to eq(1)
  387. expect(result.first.title).to eq("first note")
  388. expect(result.first).to be_a(Agents::EvernoteAgent::Note)
  389. end
  390. end
  391. context "last_checked_at is set" do
  392. context "notes_with_tags is not set" do
  393. it "only returns notes updated since then" do
  394. allow(en_note_store).to receive(:findNotesMetadata) { OpenStruct.new(notes: [note1, note3]) }
  395. result = search_with_time.notes
  396. expect(result.size).to eq(1)
  397. expect(result.first.title).to eq("first note")
  398. end
  399. end
  400. context "notes_with_tags is set" do
  401. it "returns notes updated since then or notes with recently added tags" do
  402. note3.tagNames = ["funny", "comic"]
  403. allow(en_note_store).to receive(:findNotesMetadata) { OpenStruct.new(notes: [note1, note3]) }
  404. result = search_with_time_and_tags.notes
  405. expect(result.size).to eq(2)
  406. expect(result.last.title).to eq("third note")
  407. end
  408. end
  409. end
  410. end
  411. describe "#create_filter" do
  412. it "builds an evernote search filter using search grammar" do
  413. filter = search.create_filter
  414. expect(filter.words).to eq("notebook:\"xkcd\" tag:funny tag:comic")
  415. end
  416. end
  417. end
  418. describe Agents::EvernoteAgent::Note do
  419. let(:resource) {
  420. OpenStruct.new(mime: "image/png",
  421. attributes: OpenStruct.new(sourceURL: "http://imgs.xkcd.com/comics/xkcd_survey.png", fileName: "xkcd_survey.png"))
  422. }
  423. let(:en_note_attributes) {
  424. OpenStruct.new(source: "web.clip", sourceURL: "http://xkcd.com/1572/")
  425. }
  426. let(:en_note) {
  427. OpenStruct.new(title: "xkcd Survey",
  428. tagNames: ["funny", "data"],
  429. content: "The xkcd Survey: Big Data for a Big Planet",
  430. attributes: en_note_attributes,
  431. resources: [resource])
  432. }
  433. describe "#attr" do
  434. let(:note) {
  435. Agents::EvernoteAgent::Note.new(en_note, "xkcd", ["funny", "data"])
  436. }
  437. context "when no option is set" do
  438. it "returns a hash with title, tags, notebook, source and source url" do
  439. expect(note.attr).to eq(
  440. {
  441. title: en_note.title,
  442. notebook: "xkcd",
  443. tags: ["funny", "data"],
  444. source: en_note.attributes.source,
  445. source_url: en_note.attributes.sourceURL
  446. }
  447. )
  448. end
  449. end
  450. context "when include_content is set to true" do
  451. it "includes content" do
  452. note_attr = note.attr(include_content: true)
  453. expect(note_attr[:content]).to eq(
  454. "The xkcd Survey: Big Data for a Big Planet"
  455. )
  456. end
  457. end
  458. context "when include_resources is set to true" do
  459. it "includes resources" do
  460. note_attr = note.attr(include_resources: true)
  461. expect(note_attr[:resources].first).to eq(
  462. {
  463. url: resource.attributes.sourceURL,
  464. name: resource.attributes.fileName,
  465. mime_type: resource.mime
  466. }
  467. )
  468. end
  469. end
  470. end
  471. end
  472. end