<NME> client_spec.rb
<BEF> require "spec_helper"
require "oauth"

describe Goodreads::Client do
  let(:client)  { Goodreads::Client.new(api_key: "SECRET_KEY") }
  before(:each) { Goodreads.reset_configuration }

  describe "#new" do
    it "requires an argument" do
      expect { Goodreads::Client.new(nil) }
        .to raise_error(ArgumentError, "Options hash required.")
    end

    it "requires a hash argument" do
      expect { Goodreads::Client.new("foo") }
        .to raise_error(ArgumentError, "Options hash required.")
    end
  end

  describe "#oauth_configured?" do
    it "is true when OAuth token provided to constructor" do
      client = Goodreads::Client.new(oauth_token: "a token")
      expect(client.oauth_configured?).to be true
    end

    it "is true when oauth token is not provided to constructor" do
      client = Goodreads::Client.new(api_key: "SECRET_KEY")
      expect(client.oauth_configured?).to be false
    end
  end

  describe "#request" do
    context "with oauth token" do
      it "makes an oauth request" do
        oauth_token = double
        response = double

        allow(oauth_token).to receive(:request) { response }
        allow(response).to receive(:body) { fixture("book.xml") }

        client = Goodreads::Client.new(oauth_token: oauth_token)
        expect(client.book(123)).to be_a Hash
      end
    end

    context "without oauth token" do
      before do
        allow(client).to receive(:http_request) {
          Hash.from_xml(fixture("book.xml"))["GoodreadsResponse"]
        }
      end

      it "makes a request" do
        expect(client.book(123)).to be_a Hash
      end
    end
  end

  describe "#book_by_isbn" do
    before { stub_with_key_get("/book/isbn", { isbn: "0307463745" }, "book.xml") }

    it "returns a book by isbn" do
      book = client.book_by_isbn("0307463745")

      expect(book).to respond_to :id
      expect(book).to respond_to :title
    end

    context "when book does not exist" do
      before do
        stub_request(:get, "https://www.goodreads.com/book/isbn?format=xml&isbn=123456789&key=SECRET_KEY")
          .to_return(status: 404, body: "", headers: {})
      end

      it "raises Goodreads::NotFound" do
        expect { client.book_by_isbn("123456789") }.to raise_error(Goodreads::NotFound)
      end
    end
  end

  describe "#search_books" do
    before { stub_with_key_get("/search/index", { q: "Rework" }, "search_books_by_name.xml") }

    it "returns book search results" do
      result = client.search_books("Rework")

      expect(result).to be_a(Hashie::Mash)
      expect(result).to respond_to(:query)
      expect(result).to respond_to(:total_results)
      expect(result).to respond_to(:results)
      expect(result.results).to respond_to(:work)
      expect(result.query).to eq("Rework")
      expect(result.results.work.size).to eq(3)
      expect(result.results.work.first.id).to eq(6_928_276)
    end
  end

  describe "#book" do
    before { stub_with_key_get("/book/show", { id: "6732019" }, "book.xml") }

    it "returns a book by goodreads id" do
      expect { client.book("6732019") }.not_to raise_error
    end
  end

  describe "#book_by_title" do
    before { stub_with_key_get("/book/title", { title: "Rework" }, "book.xml") }

    it "returns a book by title" do
      expect { client.book_by_title("Rework") }.not_to raise_error
    end
  end

  describe "#recent_reviews" do
    before { stub_with_key_get("/review/recent_reviews", {}, "recent_reviews.xml") }

    it "returns recent reviews" do
      reviews = client.recent_reviews

      expect(reviews).to be_an(Array)
      expect(reviews).to_not be_empty
      expect(reviews.first).to respond_to(:id)
    end

    context "with skip_cropped: true" do
      before { stub_with_key_get("/review/recent_reviews", {}, "recent_reviews.xml") }

      it "returns only full reviews" do
        reviews = client.recent_reviews(skip_cropped: true)
        expect(reviews).to be_an(Array)
        expect(reviews).to_not be_empty
      end
    end
  end

  describe "#review" do
    before { stub_with_key_get("/review/show", { id: "166204831" }, "review.xml") }

    it "returns review details" do
      review = client.review("166204831")

      expect(review).to be_a(Hashie::Mash)
      expect(review.id).to eq("166204831")
    end

    context "when review does not exist" do
      before do
        stub_request(:get, "https://www.goodreads.com/review/show?format=xml&id=12345&key=SECRET_KEY")
          .to_return(status: 404, body: "", headers: {})
      end

      it "raises Goodreads::NotFound" do
        expect { client.review("12345") }.to raise_error(Goodreads::NotFound)
      end
    end
  end

  describe "#reviews" do
    subject { client.reviews(id: user_id, shelf: shelf) }
    let(:user_id) { "5076380" }
    let(:shelf) { "to-read" }

    before { stub_with_key_get("/review/list", { v: "2", id: user_id, shelf: shelf }, response_fixture) }
    let(:response_fixture) { "reviews.xml" }

    it "returns a list of reviews" do
      expect(subject).to be_an(Array)
      expect(subject.count).to eq(2)
      expect(subject.first).to be_a(Hashie::Mash)
      expect(subject.first.keys).to include(*%w(book rating started_at read_at))
      expect(subject.map(&:id)).to eq(%w(1371624338 1371623371))
    end

    context "when there are no more reviews" do
      let(:response_fixture) { "reviews_empty.xml" }

      it "returns an empty array" do
        expect(subject).to be_an(Array)
        expect(subject.count).to eq(0)
      end
    end

    context "when user does not exist" do
      before do
        url_params = "v=2&format=xml&id=#{user_id}&key=SECRET_KEY&shelf=#{shelf}"
        stub_request(:get, "https://www.goodreads.com/review/list?#{url_params}")
          .to_return(status: 404, body: "", headers: {})
      end

      it "raises Goodreads::NotFound" do
        expect { subject }.to raise_error(Goodreads::NotFound)
      end
    end
  end

  describe "#user_review" do
    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }

    it "returns a user's existing review" do
      stub_request(:get, "https://www.goodreads.com/review/show_by_user_and_book.xml?book_id=50&user_id=1&v=2")
        .to_return(status: 200, body: fixture("review_show_by_user_and_book.xml"))

      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)
      review = client.user_review(1, 50)

      expect(review.id).to eq("21")
      expect(review.book.id).to eq(50)

      expect(review.rating).to eq("5")
      expect(review.body.strip).to eq("")
      expect(review.date_added).to eq("Tue Aug 29 11:20:01 -0700 2006")
    end
  end

  describe "#author" do
    before { stub_with_key_get("/author/show", { id: "18541" }, "author.xml") }

    it "returns author details" do
      author = client.author("18541")

      expect(author).to be_a(Hashie::Mash)
      expect(author.id).to eq("18541")
      expect(author.name).to eq("Tim O'Reilly")
      expect(author.link).to eq("http://www.goodreads.com/author/show/18541.Tim_O_Reilly")
      expect(author.fans_count).to eq(109)
      expect(author.image_url).to eq("http://photo.goodreads.com/authors/1199698411p5/18541.jpg")
      expect(author.small_image_url).to eq("http://photo.goodreads.com/authors/1199698411p2/18541.jpg")
      expect(author.about).to eq("")
      expect(author.influences).to eq("")
      expect(author.works_count).to eq("34")
      expect(author.gender).to eq("male")
      expect(author.hometown).to eq("Cork")
      expect(author.born_at).to eq("1954/06/06")
      expect(author.died_at).to be_nil
    end

    context "when author does not exist" do
      before do
        stub_request(:get, "https://www.goodreads.com/author/show?format=xml&id=12345&key=SECRET_KEY")
          .to_return(status: 404, body: "", headers: {})
      end

      it "raises Goodreads::NotFound" do
        expect { client.author("12345") }.to raise_error(Goodreads::NotFound)
      end
    end
  end

  describe "#author_by_name" do
    before do
      stub_with_key_get("/api/author_url/Orson%20Scott%20Card", { id: "Orson Scott Card" }, "author_by_name.xml")
    end

    it "returns author details" do
      author = client.author_by_name("Orson Scott Card")

      expect(author).to be_a(Hashie::Mash)
      expect(author.id).to eq("589")
      expect(author.name).to eq("Orson Scott Card")
      group_list.should be_a Hashie::Mash
      group_list.total.should eq '107'
      group_list.group!.count.should eq 50
      group_list.group[0].id.should eq "1"
      group_list.group[0].title.should eq 'Goodreads Feedback'
      group_list.group[1].id.should eq "220"
    end
  end
end
      author = client.author_books("18541")

      expect(author).to be_a(Hashie::Mash)
      expect(author.id).to eq("18541")
      expect(author.name).to eq("Tim O'Reilly")
      expect(author.link).to eq("https://www.goodreads.com/author/show/18541.Tim_O_Reilly")

      expect(author.books.book.size).to eq(30)
    end
  end

  describe "#user" do
    before { stub_with_key_get("/user/show", { id: "878044" }, "user.xml") }

    it "returns user details" do
      user = client.user("878044")

      expect(user).to be_a(Hashie::Mash)
      expect(user.id).to eq("878044")
      expect(user.name).to eq("Jan")
      expect(user.user_name).to eq("janmt")
    end

    context "when user does not exist" do
      before do
        stub_request(:get, "https://www.goodreads.com/user/show?format=xml&id=12345&key=SECRET_KEY")
          .to_return(status: 404, body: "", headers: {})
      end

      it "raises Goodreads::NotFound" do
        expect { client.user("12345") }.to raise_error(Goodreads::NotFound)
      end
    end
  end

  describe "#friends" do
    before do
      allow(client).to receive(:oauth_request)
        .and_return(Hash.from_xml(fixture("friends.xml"))["GoodreadsResponse"])
    end

    it "returns friend details" do
      friends = client.friends("878044")

      expect(friends).to be_an_instance_of(Hashie::Mash)
      expect(friends).to respond_to(:user)
      expect(friends.user.size).to eq(friends.end.to_i)
      expect(friends.user.first).to respond_to(:name)
    end
  end

  describe "#shelf" do
    it "returns list of books for a user's specified shelf" do
      stub_with_key_get("/review/list/1.xml", { shelf: "to-read", v: "2" }, "to-read.xml")

      shelf = client.shelf("1", "to-read")

      expect(shelf).to respond_to(:start)
      expect(shelf).to respond_to(:end)
      expect(shelf).to respond_to(:total)
      expect(shelf).to respond_to(:books)

      expect(shelf.start).to eq(1)
      expect(shelf.end).to eq(20)
      expect(shelf.total).to eq(40)
      expect(shelf.books.length).to eq(20)
      expect(shelf.books.first.id).to eq("45590939")
      expect(shelf.books.first.book.title.strip).to eq("The Demon-Haunted World: Science as a Candle in the Dark")
    end

    it "paginates book lists from a user's shelf" do
      stub_with_key_get("/review/list/1.xml", { shelf: "to-read", v: "2", page: "2" }, "to-read-p2.xml")

      shelf = client.shelf("1", "to-read", page: 2)

      expect(shelf.start).to eq(21)
      expect(shelf.end).to eq(40)
      expect(shelf.total).to eq(40)
      expect(shelf.books.length).to eq(20)
      expect(shelf.books.first.id).to eq("107804211")
      expect(shelf.books.first.book.title).to match(/Your Money or Your Life/)
    end

    it "returns an empty array when shelf is empty" do
      stub_with_key_get("/review/list/1.xml", { shelf: "to-read", v: "2" }, "empty.xml")

      shelf = client.shelf("1", "to-read")

      expect(shelf.start).to eq(0)
      expect(shelf.end).to eq(0)
      expect(shelf.total).to eq(0)
      expect(shelf.books.length).to eq(0)
    end
  end

  describe "#add_to_shelf" do
    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }

    it "adds a book to a user's shelf" do
      stub_request(:post, "https://www.goodreads.com/shelf/add_to_shelf.xml")
        .with(:body => {"book_id"=>"456", "name"=>"read", "v"=>"2"})
        .to_return(status: 201, body: fixture("shelf_add_to_shelf.xml"))

      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)
      review = client.add_to_shelf(456, "read")

      expect(review.id).to eq(2416981504)
      expect(review.book_id).to eq(456)

      expect(review.rating).to eq(0)
      expect(review.body).to be nil
      expect(review.body_raw).to be nil
      expect(review.spoiler).to be false

      expect(review.shelves.size).to eq(1)
      expect(review.shelves.first.name).to eq("read")
      expect(review.shelves.first.id).to eq(269274694)
      expect(review.shelves.first.exclusive).to be true
      expect(review.shelves.first.sortable).to be false


      expect(review.read_at).to be nil
      expect(review.started_at).to be nil
      expect(review.date_added).to eq("Thu Jun 07 19:58:19 -0700 2018")
      expect(review.updated_at).to eq("Thu Jun 07 19:58:53 -0700 2018")

      expect(review.body).to be nil
      expect(review.body_raw).to be nil
      expect(review.spoiler).to be false
    end
  end

  describe "#create_review" do
    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }

    it "creates a new review for a book" do
      stub_request(:post, "https://www.goodreads.com/review.xml")
        .with(:body => {
          "book_id"=>"456",
          "review" => {
            "rating" => "3",
            "review" => "Good book.",
            "read_at" => "2018-01-02",
          },
          "shelf" => "read",
          "v"=>"2",
        })
        .to_return(status: 201, body: fixture("review_create.xml"))

      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)
      review = client.create_review(456, {
        :review => "Good book.",
        :rating => 3,
        :read_at => Time.parse('2018-01-02'),
        :shelf => "read",
      })

      expect(review.id).to eq("67890")
      expect(review.book.id).to eq(456)
      expect(review.rating).to eq("3")
      expect(review.body).to eq("Good book.")
    end
  end

  describe "#edit_review" do
    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }

    it "creates a new review for a book" do
      stub_request(:post, "https://www.goodreads.com/review/67890.xml")
        .with(:body => {
          "finished" => "true",
          "review" => {
            "rating" => "5",
            "review" => "Fantastic book.",
            "read_at" => "2018-04-15",
          },
          "shelf" => "read",
          "v"=>"2",
        })
        .to_return(status: 201, body: fixture("review_update.xml"))

      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)
      review = client.edit_review(67890, {
        :finished => true,
        :review => "Fantastic book.",
        :rating => 5,
        :read_at => Time.parse('2018-04-15'),
        :shelf => "read",
      })

      expect(review.id).to eq("67890")
      expect(review.book.id).to eq(456)
      expect(review.rating).to eq("5")
      expect(review.body).to eq("Fantastic book.")
    end
  end

  describe "#shelves" do
    it "returns list of user's shelves" do
      stub_with_key_get("/shelf/list.xml", { user_id: 123, v: "2" }, "shelf_list.xml")

      response = client.shelves(123)

      expect(response).to respond_to(:start)
      expect(response).to respond_to(:end)
      expect(response).to respond_to(:total)
      expect(response).to respond_to(:shelves)

      expect(response.start).to eq(1)
      expect(response.end).to eq(3)
      expect(response.total).to eq(3)
      expect(response.shelves.length).to eq(3)
      expect(response.shelves.first.id).to eq(9049904)
      expect(response.shelves.first.name).to eq("read")
    end
  end

  describe "#user_id" do
    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }
    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }

    before do
      stub_request(:get, "https://www.goodreads.com/api/auth_user")
        .to_return(status: 200, body: fixture("oauth_response.xml"), headers: {})
    end

    it "returns id of the user with oauth authentication" do
      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)
      expect(client.user_id).to eq("2003928")
    end
  end

  describe "#group" do
    before { stub_with_key_get("/group/show", { id: "1" }, "group.xml") }

    it "returns group details" do
      group = client.group("1")

      expect(group).to be_a(Hashie::Mash)
      expect(group.id).to eq("1")
      expect(group.title).to eq("Goodreads Feedback")
      expect(group.access).to eq("public")
      expect(group.location).to eq("")
      expect(group.category).to eq("Business")
      expect(group.subcategory).to eq("Companies")
      expect(group.group_users_count).to eq("10335")
    end
  end

  describe "#group_list" do
    before { stub_with_key_get("/group/list", { id: "1", sort: "my_activity" }, "group_list.xml") }

    it "returns groups a given user is a member of" do
      group_list = client.group_list("1")

      expect(group_list).to be_a(Hashie::Mash)
      expect(group_list.total).to eq("107")
      expect(group_list.group.count).to eq(50)
      expect(group_list.group[0].id).to eq("1")
      expect(group_list.group[0].title).to eq("Goodreads Feedback")
      expect(group_list.group[1].id).to eq("220")
      expect(group_list.group[2].users_count).to eq("530")
    end
  end
end

<MSG> group list doc tweaks

<DFF> @@ -261,9 +261,10 @@ describe 'Client' do
       group_list.should be_a Hashie::Mash
       group_list.total.should eq '107'
       group_list.group!.count.should eq 50
-      group_list.group[0].id.should eq "1"
+      group_list.group[0].id.should eq '1'
       group_list.group[0].title.should eq 'Goodreads Feedback'
-      group_list.group[1].id.should eq "220"
+      group_list.group[1].id.should eq '220'
+      group_list.group[2].users_count.should eq '530'
     end
   end
 end

{'commit': 'diff --git a/README.md b/README.md\nindex 00210fe..47d1577 100644\n--- a/README.md\n+++ b/README.md\n@@ -131,14 +131,10 @@ List the groups a given user is a member of:\n group_list = client.group_list(\'user_id\', \'sort\')\n \n group_list.total         # => total number of groups\n-group_list.group!.count  # => count of groups returned in the request \n-```\n-\n-The `sort` parameter is optional, and defaults to `my_activity`. For other sorting options, [see here](http://www.goodreads.com/api#group.list).\n+group_list.group!.count  # => number of groups returned in the request\n \n-Get details of each of the groups in a list:\n+# Loop through the list to get details for each of the groups.\n \n-```ruby\n group_list.group.each do |g|\n   g.id                 # => group id\n   g.access             # => access settings (private, public)\n@@ -149,6 +145,8 @@ group_list.group.each do |g|\n end\n ```\n \n+The `sort` parameter is optional, and defaults to `my_activity`. For other sorting options, [see here](http://www.goodreads.com/api#group.list).\n+\n ### User ID\n \n Get the user id of the user who authorized via OAuth:\ndiff --git a/spec/client_spec.rb b/spec/client_spec.rb\nindex 0016cb3..55a043a 100644\n--- a/spec/client_spec.rb\n+++ b/spec/client_spec.rb\n@@ -261,9 +261,10 @@ describe \'Client\' do\n       group_list.should be_a Hashie::Mash\n       group_list.total.should eq \'107\'\n       group_list.group!.count.should eq 50\n-      group_list.group[0].id.should eq "1"\n+      group_list.group[0].id.should eq \'1\'\n       group_list.group[0].title.should eq \'Goodreads Feedback\'\n-      group_list.group[1].id.should eq "220"\n+      group_list.group[1].id.should eq \'220\'\n+      group_list.group[2].users_count.should eq \'530\'\n     end\n   end\n end\n', 'commit_subject': 'group list doc tweaks', 'repo_name': 'sosedoff/goodreads', 'committer': {'name': 'John Shutt', 'email': '9d4068f959cab1c724add3f287a7bbbedb26f9f9@gmail.com', 'time_sec': '1361872690', 'tz_offset': '-480', 'date': {'seconds': '1361872690'}}, 'commit_tree': '8eea191ffbea41b16725f668ee1df1a58393db22', 'commit_message': 'group list doc tweaks\n', 'language_name': 'Ruby', 'license': 'mit', 'hunks': '@@ -261,9 +261,10 @@ describe \'Client\' do\n       group_list.should be_a Hashie::Mash\n       group_list.total.should eq \'107\'\n       group_list.group!.count.should eq 50\n-      group_list.group[0].id.should eq "1"\n+      group_list.group[0].id.should eq \'1\'\n       group_list.group[0].title.should eq \'Goodreads Feedback\'\n-      group_list.group[1].id.should eq "220"\n+      group_list.group[1].id.should eq \'220\'\n+      group_list.group[2].users_count.should eq \'530\'\n     end\n   end\n end\n', 'addition_count': 3, 'deletion_count': 2, 'src_file': 'a/spec/client_spec.rb', 'tgt_file': 'b/spec/client_spec.rb', 'file_extension': '.rb', 'before_file': 'require "spec_helper"\nrequire "oauth"\n\ndescribe Goodreads::Client do\n  let(:client)  { Goodreads::Client.new(api_key: "SECRET_KEY") }\n  before(:each) { Goodreads.reset_configuration }\n\n  describe "#new" do\n    it "requires an argument" do\n      expect { Goodreads::Client.new(nil) }\n        .to raise_error(ArgumentError, "Options hash required.")\n    end\n\n    it "requires a hash argument" do\n      expect { Goodreads::Client.new("foo") }\n        .to raise_error(ArgumentError, "Options hash required.")\n    end\n  end\n\n  describe "#oauth_configured?" do\n    it "is true when OAuth token provided to constructor" do\n      client = Goodreads::Client.new(oauth_token: "a token")\n      expect(client.oauth_configured?).to be true\n    end\n\n    it "is true when oauth token is not provided to constructor" do\n      client = Goodreads::Client.new(api_key: "SECRET_KEY")\n      expect(client.oauth_configured?).to be false\n    end\n  end\n\n  describe "#request" do\n    context "with oauth token" do\n      it "makes an oauth request" do\n        oauth_token = double\n        response = double\n\n        allow(oauth_token).to receive(:request) { response }\n        allow(response).to receive(:body) { fixture("book.xml") }\n\n        client = Goodreads::Client.new(oauth_token: oauth_token)\n        expect(client.book(123)).to be_a Hash\n      end\n    end\n\n    context "without oauth token" do\n      before do\n        allow(client).to receive(:http_request) {\n          Hash.from_xml(fixture("book.xml"))["GoodreadsResponse"]\n        }\n      end\n\n      it "makes a request" do\n        expect(client.book(123)).to be_a Hash\n      end\n    end\n  end\n\n  describe "#book_by_isbn" do\n    before { stub_with_key_get("/book/isbn", { isbn: "0307463745" }, "book.xml") }\n\n    it "returns a book by isbn" do\n      book = client.book_by_isbn("0307463745")\n\n      expect(book).to respond_to :id\n      expect(book).to respond_to :title\n    end\n\n    context "when book does not exist" do\n      before do\n        stub_request(:get, "https://www.goodreads.com/book/isbn?format=xml&isbn=123456789&key=SECRET_KEY")\n          .to_return(status: 404, body: "", headers: {})\n      end\n\n      it "raises Goodreads::NotFound" do\n        expect { client.book_by_isbn("123456789") }.to raise_error(Goodreads::NotFound)\n      end\n    end\n  end\n\n  describe "#search_books" do\n    before { stub_with_key_get("/search/index", { q: "Rework" }, "search_books_by_name.xml") }\n\n    it "returns book search results" do\n      result = client.search_books("Rework")\n\n      expect(result).to be_a(Hashie::Mash)\n      expect(result).to respond_to(:query)\n      expect(result).to respond_to(:total_results)\n      expect(result).to respond_to(:results)\n      expect(result.results).to respond_to(:work)\n      expect(result.query).to eq("Rework")\n      expect(result.results.work.size).to eq(3)\n      expect(result.results.work.first.id).to eq(6_928_276)\n    end\n  end\n\n  describe "#book" do\n    before { stub_with_key_get("/book/show", { id: "6732019" }, "book.xml") }\n\n    it "returns a book by goodreads id" do\n      expect { client.book("6732019") }.not_to raise_error\n    end\n  end\n\n  describe "#book_by_title" do\n    before { stub_with_key_get("/book/title", { title: "Rework" }, "book.xml") }\n\n    it "returns a book by title" do\n      expect { client.book_by_title("Rework") }.not_to raise_error\n    end\n  end\n\n  describe "#recent_reviews" do\n    before { stub_with_key_get("/review/recent_reviews", {}, "recent_reviews.xml") }\n\n    it "returns recent reviews" do\n      reviews = client.recent_reviews\n\n      expect(reviews).to be_an(Array)\n      expect(reviews).to_not be_empty\n      expect(reviews.first).to respond_to(:id)\n    end\n\n    context "with skip_cropped: true" do\n      before { stub_with_key_get("/review/recent_reviews", {}, "recent_reviews.xml") }\n\n      it "returns only full reviews" do\n        reviews = client.recent_reviews(skip_cropped: true)\n        expect(reviews).to be_an(Array)\n        expect(reviews).to_not be_empty\n      end\n    end\n  end\n\n  describe "#review" do\n    before { stub_with_key_get("/review/show", { id: "166204831" }, "review.xml") }\n\n    it "returns review details" do\n      review = client.review("166204831")\n\n      expect(review).to be_a(Hashie::Mash)\n      expect(review.id).to eq("166204831")\n    end\n\n    context "when review does not exist" do\n      before do\n        stub_request(:get, "https://www.goodreads.com/review/show?format=xml&id=12345&key=SECRET_KEY")\n          .to_return(status: 404, body: "", headers: {})\n      end\n\n      it "raises Goodreads::NotFound" do\n        expect { client.review("12345") }.to raise_error(Goodreads::NotFound)\n      end\n    end\n  end\n\n  describe "#reviews" do\n    subject { client.reviews(id: user_id, shelf: shelf) }\n    let(:user_id) { "5076380" }\n    let(:shelf) { "to-read" }\n\n    before { stub_with_key_get("/review/list", { v: "2", id: user_id, shelf: shelf }, response_fixture) }\n    let(:response_fixture) { "reviews.xml" }\n\n    it "returns a list of reviews" do\n      expect(subject).to be_an(Array)\n      expect(subject.count).to eq(2)\n      expect(subject.first).to be_a(Hashie::Mash)\n      expect(subject.first.keys).to include(*%w(book rating started_at read_at))\n      expect(subject.map(&:id)).to eq(%w(1371624338 1371623371))\n    end\n\n    context "when there are no more reviews" do\n      let(:response_fixture) { "reviews_empty.xml" }\n\n      it "returns an empty array" do\n        expect(subject).to be_an(Array)\n        expect(subject.count).to eq(0)\n      end\n    end\n\n    context "when user does not exist" do\n      before do\n        url_params = "v=2&format=xml&id=#{user_id}&key=SECRET_KEY&shelf=#{shelf}"\n        stub_request(:get, "https://www.goodreads.com/review/list?#{url_params}")\n          .to_return(status: 404, body: "", headers: {})\n      end\n\n      it "raises Goodreads::NotFound" do\n        expect { subject }.to raise_error(Goodreads::NotFound)\n      end\n    end\n  end\n\n  describe "#user_review" do\n    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }\n    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }\n\n    it "returns a user\'s existing review" do\n      stub_request(:get, "https://www.goodreads.com/review/show_by_user_and_book.xml?book_id=50&user_id=1&v=2")\n        .to_return(status: 200, body: fixture("review_show_by_user_and_book.xml"))\n\n      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)\n      review = client.user_review(1, 50)\n\n      expect(review.id).to eq("21")\n      expect(review.book.id).to eq(50)\n\n      expect(review.rating).to eq("5")\n      expect(review.body.strip).to eq("")\n      expect(review.date_added).to eq("Tue Aug 29 11:20:01 -0700 2006")\n    end\n  end\n\n  describe "#author" do\n    before { stub_with_key_get("/author/show", { id: "18541" }, "author.xml") }\n\n    it "returns author details" do\n      author = client.author("18541")\n\n      expect(author).to be_a(Hashie::Mash)\n      expect(author.id).to eq("18541")\n      expect(author.name).to eq("Tim O\'Reilly")\n      expect(author.link).to eq("http://www.goodreads.com/author/show/18541.Tim_O_Reilly")\n      expect(author.fans_count).to eq(109)\n      expect(author.image_url).to eq("http://photo.goodreads.com/authors/1199698411p5/18541.jpg")\n      expect(author.small_image_url).to eq("http://photo.goodreads.com/authors/1199698411p2/18541.jpg")\n      expect(author.about).to eq("")\n      expect(author.influences).to eq("")\n      expect(author.works_count).to eq("34")\n      expect(author.gender).to eq("male")\n      expect(author.hometown).to eq("Cork")\n      expect(author.born_at).to eq("1954/06/06")\n      expect(author.died_at).to be_nil\n    end\n\n    context "when author does not exist" do\n      before do\n        stub_request(:get, "https://www.goodreads.com/author/show?format=xml&id=12345&key=SECRET_KEY")\n          .to_return(status: 404, body: "", headers: {})\n      end\n\n      it "raises Goodreads::NotFound" do\n        expect { client.author("12345") }.to raise_error(Goodreads::NotFound)\n      end\n    end\n  end\n\n  describe "#author_by_name" do\n    before do\n      stub_with_key_get("/api/author_url/Orson%20Scott%20Card", { id: "Orson Scott Card" }, "author_by_name.xml")\n    end\n\n    it "returns author details" do\n      author = client.author_by_name("Orson Scott Card")\n\n      expect(author).to be_a(Hashie::Mash)\n      expect(author.id).to eq("589")\n      expect(author.name).to eq("Orson Scott Card")\n      group_list.should be_a Hashie::Mash\n      group_list.total.should eq \'107\'\n      group_list.group!.count.should eq 50\n      group_list.group[0].id.should eq "1"\n      group_list.group[0].title.should eq \'Goodreads Feedback\'\n      group_list.group[1].id.should eq "220"\n    end\n  end\nend\n      author = client.author_books("18541")\n\n      expect(author).to be_a(Hashie::Mash)\n      expect(author.id).to eq("18541")\n      expect(author.name).to eq("Tim O\'Reilly")\n      expect(author.link).to eq("https://www.goodreads.com/author/show/18541.Tim_O_Reilly")\n\n      expect(author.books.book.size).to eq(30)\n    end\n  end\n\n  describe "#user" do\n    before { stub_with_key_get("/user/show", { id: "878044" }, "user.xml") }\n\n    it "returns user details" do\n      user = client.user("878044")\n\n      expect(user).to be_a(Hashie::Mash)\n      expect(user.id).to eq("878044")\n      expect(user.name).to eq("Jan")\n      expect(user.user_name).to eq("janmt")\n    end\n\n    context "when user does not exist" do\n      before do\n        stub_request(:get, "https://www.goodreads.com/user/show?format=xml&id=12345&key=SECRET_KEY")\n          .to_return(status: 404, body: "", headers: {})\n      end\n\n      it "raises Goodreads::NotFound" do\n        expect { client.user("12345") }.to raise_error(Goodreads::NotFound)\n      end\n    end\n  end\n\n  describe "#friends" do\n    before do\n      allow(client).to receive(:oauth_request)\n        .and_return(Hash.from_xml(fixture("friends.xml"))["GoodreadsResponse"])\n    end\n\n    it "returns friend details" do\n      friends = client.friends("878044")\n\n      expect(friends).to be_an_instance_of(Hashie::Mash)\n      expect(friends).to respond_to(:user)\n      expect(friends.user.size).to eq(friends.end.to_i)\n      expect(friends.user.first).to respond_to(:name)\n    end\n  end\n\n  describe "#shelf" do\n    it "returns list of books for a user\'s specified shelf" do\n      stub_with_key_get("/review/list/1.xml", { shelf: "to-read", v: "2" }, "to-read.xml")\n\n      shelf = client.shelf("1", "to-read")\n\n      expect(shelf).to respond_to(:start)\n      expect(shelf).to respond_to(:end)\n      expect(shelf).to respond_to(:total)\n      expect(shelf).to respond_to(:books)\n\n      expect(shelf.start).to eq(1)\n      expect(shelf.end).to eq(20)\n      expect(shelf.total).to eq(40)\n      expect(shelf.books.length).to eq(20)\n      expect(shelf.books.first.id).to eq("45590939")\n      expect(shelf.books.first.book.title.strip).to eq("The Demon-Haunted World: Science as a Candle in the Dark")\n    end\n\n    it "paginates book lists from a user\'s shelf" do\n      stub_with_key_get("/review/list/1.xml", { shelf: "to-read", v: "2", page: "2" }, "to-read-p2.xml")\n\n      shelf = client.shelf("1", "to-read", page: 2)\n\n      expect(shelf.start).to eq(21)\n      expect(shelf.end).to eq(40)\n      expect(shelf.total).to eq(40)\n      expect(shelf.books.length).to eq(20)\n      expect(shelf.books.first.id).to eq("107804211")\n      expect(shelf.books.first.book.title).to match(/Your Money or Your Life/)\n    end\n\n    it "returns an empty array when shelf is empty" do\n      stub_with_key_get("/review/list/1.xml", { shelf: "to-read", v: "2" }, "empty.xml")\n\n      shelf = client.shelf("1", "to-read")\n\n      expect(shelf.start).to eq(0)\n      expect(shelf.end).to eq(0)\n      expect(shelf.total).to eq(0)\n      expect(shelf.books.length).to eq(0)\n    end\n  end\n\n  describe "#add_to_shelf" do\n    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }\n    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }\n\n    it "adds a book to a user\'s shelf" do\n      stub_request(:post, "https://www.goodreads.com/shelf/add_to_shelf.xml")\n        .with(:body => {"book_id"=>"456", "name"=>"read", "v"=>"2"})\n        .to_return(status: 201, body: fixture("shelf_add_to_shelf.xml"))\n\n      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)\n      review = client.add_to_shelf(456, "read")\n\n      expect(review.id).to eq(2416981504)\n      expect(review.book_id).to eq(456)\n\n      expect(review.rating).to eq(0)\n      expect(review.body).to be nil\n      expect(review.body_raw).to be nil\n      expect(review.spoiler).to be false\n\n      expect(review.shelves.size).to eq(1)\n      expect(review.shelves.first.name).to eq("read")\n      expect(review.shelves.first.id).to eq(269274694)\n      expect(review.shelves.first.exclusive).to be true\n      expect(review.shelves.first.sortable).to be false\n\n\n      expect(review.read_at).to be nil\n      expect(review.started_at).to be nil\n      expect(review.date_added).to eq("Thu Jun 07 19:58:19 -0700 2018")\n      expect(review.updated_at).to eq("Thu Jun 07 19:58:53 -0700 2018")\n\n      expect(review.body).to be nil\n      expect(review.body_raw).to be nil\n      expect(review.spoiler).to be false\n    end\n  end\n\n  describe "#create_review" do\n    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }\n    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }\n\n    it "creates a new review for a book" do\n      stub_request(:post, "https://www.goodreads.com/review.xml")\n        .with(:body => {\n          "book_id"=>"456",\n          "review" => {\n            "rating" => "3",\n            "review" => "Good book.",\n            "read_at" => "2018-01-02",\n          },\n          "shelf" => "read",\n          "v"=>"2",\n        })\n        .to_return(status: 201, body: fixture("review_create.xml"))\n\n      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)\n      review = client.create_review(456, {\n        :review => "Good book.",\n        :rating => 3,\n        :read_at => Time.parse(\'2018-01-02\'),\n        :shelf => "read",\n      })\n\n      expect(review.id).to eq("67890")\n      expect(review.book.id).to eq(456)\n      expect(review.rating).to eq("3")\n      expect(review.body).to eq("Good book.")\n    end\n  end\n\n  describe "#edit_review" do\n    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }\n    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }\n\n    it "creates a new review for a book" do\n      stub_request(:post, "https://www.goodreads.com/review/67890.xml")\n        .with(:body => {\n          "finished" => "true",\n          "review" => {\n            "rating" => "5",\n            "review" => "Fantastic book.",\n            "read_at" => "2018-04-15",\n          },\n          "shelf" => "read",\n          "v"=>"2",\n        })\n        .to_return(status: 201, body: fixture("review_update.xml"))\n\n      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)\n      review = client.edit_review(67890, {\n        :finished => true,\n        :review => "Fantastic book.",\n        :rating => 5,\n        :read_at => Time.parse(\'2018-04-15\'),\n        :shelf => "read",\n      })\n\n      expect(review.id).to eq("67890")\n      expect(review.book.id).to eq(456)\n      expect(review.rating).to eq("5")\n      expect(review.body).to eq("Fantastic book.")\n    end\n  end\n\n  describe "#shelves" do\n    it "returns list of user\'s shelves" do\n      stub_with_key_get("/shelf/list.xml", { user_id: 123, v: "2" }, "shelf_list.xml")\n\n      response = client.shelves(123)\n\n      expect(response).to respond_to(:start)\n      expect(response).to respond_to(:end)\n      expect(response).to respond_to(:total)\n      expect(response).to respond_to(:shelves)\n\n      expect(response.start).to eq(1)\n      expect(response.end).to eq(3)\n      expect(response.total).to eq(3)\n      expect(response.shelves.length).to eq(3)\n      expect(response.shelves.first.id).to eq(9049904)\n      expect(response.shelves.first.name).to eq("read")\n    end\n  end\n\n  describe "#user_id" do\n    let(:consumer) { OAuth::Consumer.new("API_KEY", "SECRET_KEY", site: "https://www.goodreads.com") }\n    let(:token)    { OAuth::AccessToken.new(consumer, "ACCESS_TOKEN", "ACCESS_SECRET") }\n\n    before do\n      stub_request(:get, "https://www.goodreads.com/api/auth_user")\n        .to_return(status: 200, body: fixture("oauth_response.xml"), headers: {})\n    end\n\n    it "returns id of the user with oauth authentication" do\n      client = Goodreads::Client.new(api_key: "SECRET_KEY", oauth_token: token)\n      expect(client.user_id).to eq("2003928")\n    end\n  end\n\n  describe "#group" do\n    before { stub_with_key_get("/group/show", { id: "1" }, "group.xml") }\n\n    it "returns group details" do\n      group = client.group("1")\n\n      expect(group).to be_a(Hashie::Mash)\n      expect(group.id).to eq("1")\n      expect(group.title).to eq("Goodreads Feedback")\n      expect(group.access).to eq("public")\n      expect(group.location).to eq("")\n      expect(group.category).to eq("Business")\n      expect(group.subcategory).to eq("Companies")\n      expect(group.group_users_count).to eq("10335")\n    end\n  end\n\n  describe "#group_list" do\n    before { stub_with_key_get("/group/list", { id: "1", sort: "my_activity" }, "group_list.xml") }\n\n    it "returns groups a given user is a member of" do\n      group_list = client.group_list("1")\n\n      expect(group_list).to be_a(Hashie::Mash)\n      expect(group_list.total).to eq("107")\n      expect(group_list.group.count).to eq(50)\n      expect(group_list.group[0].id).to eq("1")\n      expect(group_list.group[0].title).to eq("Goodreads Feedback")\n      expect(group_list.group[1].id).to eq("220")\n      expect(group_list.group[2].users_count).to eq("530")\n    end\n  end\nend\n', 'message': 'group list doc tweaks\n', 'author_name': 'John Shutt', 'lang': 'rb'}