describe("Zotero.Feeds", function () {
	
	after(function* () {
		yield clearFeeds();
	});
	
	describe('#importFromOPML', function () {
		var opmlUrl = getTestDataUrl("feeds.opml");
		var opmlString;
		
		before(function* (){
			opmlString = yield Zotero.File.getContentsFromURLAsync(opmlUrl);
			sinon.stub(Zotero.Feeds, 'updateFeeds').resolves();
		});
		
		beforeEach(function* () {
			yield clearFeeds();
		});
		
		after(function () {
			Zotero.Feeds.updateFeeds.restore();
		});
		
		it('imports feeds correctly', async function () {
			let shouldExist = {
				"http://example.com/feed1.rss": "A title 1",
				"http://example.com/feed2.rss": "A title 2",
				"http://example.com/feed3.rss": "A title 3",
				"http://example.com/feed4.rss": "A title 4",
				"http://example.com/feed5.rss": Zotero.getString('pane.collections.untitled')
			};
			await Zotero.Feeds.importFromOPML(opmlString);
			let feeds = Zotero.Feeds.getAll();
			for (let feed of feeds) {
				assert.equal(shouldExist[feed.url], feed.name, "Feed exists and title matches");
				delete shouldExist[feed.url];
			}
			assert.equal(Object.keys(shouldExist).length, 0, "All feeds from opml have been created");
		});
		
		it("doesn't fail if some feeds already exist", async function () {
			await createFeed({url: "http://example.com/feed1.rss"});
			await Zotero.Feeds.importFromOPML(opmlString)
		});
	});
	
	describe("#restoreFromJSON", function () {
		var json, expiredFeedURL, existingFeedURL;
		
		beforeEach(function* () {
			yield clearFeeds();
		
			json = {};
			for (let i = 0; i < 2; i++) {
				let url = "http://" + Zotero.Utilities.randomString(10, 'abcdefgh') + ".com/feed.rss";
				json[url] = {
					url,
					name: Zotero.Utilities.randomString(),
					refreshInterval: 5,
					cleanupReadAfter: 3,
					cleanupUnreadAfter: 30,
				};
				if (i == 0) {
					existingFeedURL = url;
					yield createFeed({url});
				}
			}
			expiredFeedURL = (yield createFeed()).url;
		});
		
		it("restores correctly when merge is true", async function () {
			let feeds = Zotero.Feeds.getAll();
			assert.equal(feeds.length, 2);
			
			await Zotero.Feeds.restoreFromJSON(json, true);
			feeds = Zotero.Feeds.getAll();
			
			for (let url in json) {
				let feed = Zotero.Feeds.getByURL(url);
				assert.ok(feed, "new feed created");
			}	
			
			let expiredFeed = Zotero.Feeds.getByURL(expiredFeedURL);
			assert.ok(expiredFeed, "does not remove feeds not in JSON");

			let existingFeed = Zotero.Feeds.getByURL(existingFeedURL);
			assert.ok(existingFeed, "does not remove feeds in database and JSON");
		});
		
		it("restores correctly when merge is false", async function () {
			let feeds = Zotero.Feeds.getAll();
			assert.equal(feeds.length, 2);
			
			await Zotero.Feeds.restoreFromJSON(json);
			feeds = Zotero.Feeds.getAll();
			
			for (let url in json) {
				let feed = Zotero.Feeds.getByURL(url);
				assert.ok(feed, "new feed created");
			}	
			
			let expiredFeed = Zotero.Feeds.getByURL(expiredFeedURL);
			assert.notOk(expiredFeed, "removes feeds not in JSON");

			let existingFeed = Zotero.Feeds.getByURL(existingFeedURL);
			assert.ok(existingFeed, "does not remove feeds in database and JSON");
		});
	});

	describe("#haveFeeds()", function () {
		it("should return false for a DB without feeds", async function () {
			await clearFeeds();
			assert.isFalse(Zotero.Feeds.haveFeeds(), 'no feeds in empty DB');
			
			let group = await createGroup();
			
			assert.isFalse(Zotero.Feeds.haveFeeds(), 'no feeds in DB with groups');
		});
		it("should return true for a DB containing feeds", async function () {
			let feed = await createFeed();
			
			assert.isTrue(Zotero.Feeds.haveFeeds());
		});
	});
	describe("#getAll()", function () {
		it("should return an empty array for a DB without feeds", async function () {
			await clearFeeds();
			let feeds = Zotero.Feeds.getAll();
			assert.lengthOf(feeds, 0, 'no feeds in an empty DB');
			
			let group = await createGroup();
			
			feeds = Zotero.Feeds.getAll();
			assert.lengthOf(feeds, 0, 'no feeds in DB with group libraries');
		});
		it("should return an array of feeds", async function () {
			await clearFeeds();
			let feed1 = await createFeed();
			let feed2 = await createFeed();
			
			let feeds = Zotero.Feeds.getAll();
			assert.lengthOf(feeds, 2);
			assert.sameMembers(feeds, [feed1, feed2]);
		});
	});
	
	describe('#getByURL', function () {
		it("should return a feed by url", async function () {
			let url = 'http://' + Zotero.Utilities.randomString(10, 'abcdefg') + '.com/feed.rss';
			await createFeed({url});
			let feed = Zotero.Feeds.getByURL(url);
			assert.ok(feed);
			assert.equal(feed.url, url);
		});
		it("should return undefined if feed does not exist", async function () {
			var feed;
			assert.doesNotThrow(function () {
				feed = Zotero.Feeds.getByURL('doesnotexist');
			});
			assert.isUndefined(feed);
		});
	});
	describe('#updateFeeds', function () {
		var freshFeed, recentFeed, oldFeed;
		var _updateFeed;
	
		before(function* () {
			yield clearFeeds();
		
			sinon.stub(Zotero.Feeds, 'scheduleNextFeedCheck').resolves();
			_updateFeed = sinon.stub(Zotero.Feed.prototype, '_updateFeed').resolves();
			let url = getTestDataUrl("feed.rss");
			
			freshFeed = yield createFeed({refreshInterval: 2});
			freshFeed._feedUrl = url;
			freshFeed.lastCheck = null;
			yield freshFeed.saveTx();
			
			recentFeed = yield createFeed({refreshInterval: 2});
			recentFeed._feedUrl = url;
			recentFeed.lastCheck = Zotero.Date.dateToSQL(new Date(), true);
			yield recentFeed.saveTx();
			
			oldFeed = yield createFeed({refreshInterval: 2});
			oldFeed._feedUrl = url;
			oldFeed.lastCheck = Zotero.Date.dateToSQL(new Date(Date.now() - 1000*60*60*6), true);
			yield oldFeed.saveTx();
			
			yield Zotero.Feeds.updateFeeds();
			assert.isTrue(_updateFeed.called);
		});
		
		after(function () {
			Zotero.Feeds.scheduleNextFeedCheck.restore();
			_updateFeed.restore();
		});
		
		it('should update feeds that have never been updated', function () {
			for (var feed of _updateFeed.thisValues) {
				if (feed.id == freshFeed.id) {
					break;
				}
			}
			assert.isTrue(feed._updateFeed.called);
		});
		it('should update feeds that need updating since last check', function () {
			for (var feed of _updateFeed.thisValues) {
				if (feed.id == oldFeed.id) {
					break;
				}
			}
			assert.isTrue(feed._updateFeed.called);
		});
		it("should not update feeds that don't need updating", function () {
			for (var feed of _updateFeed.thisValues) {
				if (feed.id != recentFeed.id) {
					break;
				}
				// should never reach
				assert.ok(null, "does not update feed that did not need updating")
			}
		});
	});
	describe('#scheduleNextFeedCheck()', function () {
		it('schedules next feed check', async function () {
			sinon.spy(Zotero.Feeds, 'scheduleNextFeedCheck');
			
			await clearFeeds();
			let feed = await createFeed({refreshInterval: 1});
			feed._set('_feedLastCheck', Zotero.Date.dateToSQL(new Date(), true));
			await feed.saveTx();

			await Zotero.Feeds.scheduleNextFeedCheck();
			
			// Allow a propagation delay of 5000ms
			assert.isTrue(Zotero.Feeds._nextFeedCheckDelay - 1000 * 60 * 60 <= 5000);
			
			Zotero.Feeds.scheduleNextFeedCheck.restore();
		});
	})
})
