Giter Club home page Giter Club logo

meteor-publication-collector's Introduction

Publication Collector

CircleCI

This package makes testing publications in Meteor easier and nicer.

Instead of resorting to exporting or exposing your publication functions for doing testing, this package lets you "subscribe" to a given publication and assert its returned results.

Installation

meteor add johanbrook:publication-collector

Usage

This package is server-only and can't be imported on the client.

// server/myPublication.test.js

import { PublicationCollector } from 'meteor/johanbrook:publication-collector';

describe('myPublication', function() {
  it('should publish 10 documents', function(done) {
    const collector = new PublicationCollector({ userId: Random.id() });

    collector.collect('myPublication', firstPublicationArg, secondPublicationArg, (collections) => {
      assert.equal(collections.myCollection.length, 10);
      done();
    });
  });
});

PublicationCollector

const collector = new PublicationCollector(opts);

opts may have the following attributes:

  • userId: Add a this.userId to the publication's context
  • delayInMs: By default, collect callbacks are called when the publication is ready. If you use this option, the callbacks will be called delayInMs milliseconds after the publication is ready.

An instance of PublicationCollector also is an EventEmitter, and emits a ready event when the publication is marked as ready.

PublicationCollector.collect -> Promise

collector.collect(publicationName, [publicationArgs..., callback]);
  • publicationName (String): the name of the publication (String)
  • publicationArgs: zero or more arguments to the publication
  • callback (Function): Optional. The function to be called when the publication is ready. Will be called with a collections object.

Returns a Promise which resolves to a collections object.

The collections value is an object containing key:value pairs where the key is the name of a collection that the publication published and the value is an array of the documents that were published in that collection.

collector.collect('myPublication', firstPublicationArg, secondPublicationArg, (collections) => {
  assert.equal(collections.myCollection.length, 10);
});

or use Promises:

const collector = new PublicationCollector();

collector.collect('myPublication')
  .then(collections => {
    // assertions..
  })
  .catch(ex => /* error handling */);

// Or async/await style
const collections = await collector.collect('myPublication');
// assertions..

Development

npm install

Follow .eslintrc

Tests

Run tests once with

npm test

Run tests in watch mode (in console) with

npm run test:dev

History

This project was originally a part of MDG's Todos example Meteor app, but later extracted as a separate test package.

Based on https://github.com/stubailo/meteor-rest/blob/devel/packages/rest/http-subscription.js.

Releases

  • 1.1.0
    • Pin versions to Meteor@>=1.3.
    • Throw error when there's no publication for the provided name.
    • Upgrade dependencies. ย - Add support for Promises in the .collect() method. Note: This breaks tests that rely on errors being thrown in the collect() method (see #36).
  • 1.0.10 - Always stop the publication when an error is thrown in the PublicationCollector callback. Thanks @SimonSimCity !
  • 1.0.9 - Fix bug in 1.0.8 regarding empty array return. Thanks @nkahnfr !
  • 1.0.8 - Fix support for publications returning nothing (an empty array). Thanks @ziedmahdi !
  • 1.0.7 - Fix compatibility with peerlibrary:reactive-publish's _isDeactivated function in publications (#20, thanks @jaskinn!).
  • 1.0.6 - Fix an issue with "ready" event being emitted more than once (#16). Thanks @nkahnfr!
  • 1.0.5 - Fix an issue when publish handlers are using default arguments (#15). Thanks @dmihal!
  • 1.0.4 - Don't try to operate on a document that doesn't exist in changed callback. Thanks @zenweasel, from #13!
  • 1.0.3 - Fix compatibility with peerlibrary:reactive-publish package (bug #3), fixed in #10. Thanks @hexsprite!
  • 1.0.2 - Fix bug where ready() wasn't called if there were no results from a publication handler.
  • 1.0.1
    • Fixes inconsistent results from publication collector (thanks @PhilippSpo in #2).
    • Return an empty array if there are no returned documents from a publication (#5).
    • Accept Mongo.ObjectID as _id attribute (#8).
  • 1.0.0 - First public release.

To do

  • Make tests pass.
  • More docs.
  • Support Promises.

meteor-publication-collector's People

Contributors

bastianplatz avatar brookback avatar clemenshelm avatar dmihal avatar hexsprite avatar johanbrook avatar lorensr avatar simonsimcity avatar stephane-ein avatar zenweasel avatar ziedmahdi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

meteor-publication-collector's Issues

Exception when publication returns an empty array.

When testing the published data for this collection, userId = null, I get Publish function can only return a Cursor or an array of Cursors

Meteor.publish('kibanaDashboards', function () {
    //make sure that the user is logged in
    if (this.userId) {
        return KibanaDashboards.find({});
    } else {
        this.stop();
        return [];
    }
});

I think the collector should expect empty arrays and null values.

Failing to resolve promise or call callback when an publication is empty

Hi guys. I spent a couple of days on this and finally narrowed down the problem. I have this publication:

Meteor.publish("game_requests",  function() {
  const user = Meteor.user();
  if (!user || !user.loggedOn) return [];
  if (Game.isPlayingGame(user)) return GameRequestCollection.find({ _id: "0" }); //return [];

  const id = user._id;
  if (!id) return [];
  return GameRequestCollection.find(
    {
      $or: [
        { challenger_id: id },
        { receiver_id: id },
        { owner: id },
        { matchingusers: id },
        { type: "seek" }
      ]
    },
    { fields: { matchingusers: 0 } }
  );
});

When I return find({_id: "0"}), collector.collect returns correctly.
When I return [], collector.collect does not resolve or call the callback.

According to the Meteor documentation, return [] is the accepted practice when returning an empty publication, right? Am I missing something, or is this an actual issue?

Thank you,
David Logan

collector.collect() doesn't throw in 1.1.0

Hi,

We've just updated Publication Collector to 1.1.0 and it's broken a few of our tests. Given that you've added support for Promises, I'd say this is probably something you'd consider "expected behaviour", but I was thinking you might want to update the changelog/history to note that it's a potentially breaking change.

Example of the problem:

// FooBarQux is a publication that has a check(argument, String) call for type-checking arguments
describe('FooBarQux subscription', () => {
    it('Takes a String argument', () => {
      const collector = new PublicationCollector();
      expect(() => collector.collect('OrganisationDetail'))
      .to.throw(/Match error/);

      expect(() => collector.collect('FooBarQux', {
        foo: 'bar',
      }))
      .to.throw(/Match error/);
    });
  });

This test has regressed because collector.collect() no longer re-throws errors that are thrown by the publication; instead it returns a promise and rejects it.

This is obviously expected behaviour and we've updated our test suite accordingly, but I can only assume that anybody else testing for publication errors has been doing something similar, so I'd argue that this is potentially a breaking change?

Error when trying to use stubbed collections for publication tests

Hi,

I'm trying to use the hwillson:stub-collections package for publication tests. I would like to use stubbed collections to have the tests run quicker.

The stacktrace of the error I get is:

Can't publish a cursor from a collection without a name. at [object Object].LocalCollection.Cursor._publishCursor (packages/minimongo.js:265:11) at packages/johanbrook:publication-collector/publication-collector.js:49:20 at Array.map (native) at PublicationCollector.collect (packages/johanbrook:publication-collector/publication-collector.js:44:47)

The error occurs because the hwillson:stub-collections package creates an in-memory (minimongo) collection to replace the real collection and that is achieved by creating a new Meteor collection without a name (null).

I realise this problem isn't because of your package, but I am hoping you have some insight to share?

Thanks!

Does not collect results on `ready()`

When a collection is published using the low-level publication API, calling this.ready() does not seem to have any effect. My tests are reporting: Error: timeout of 2000ms exceeded.. I can't replicate this error when running the app and subscribing to the same publication. I noticed this package's tests are testing publications that all use return, so it may be worth checking this use case.

Thanks for the package, though. It makes testing so much easier in the majority of use cases.

ES6 Error

this.userId is undefined when using arrow function as argument of the publication.

// pub.js
Meteor.publish('posts', function({ _id }) {
  const user = this.userId; // working correctly
  return Posts.find({ _id, user });
});
// pub.js
Meteor.publish('posts', ({ _id }) => {
  const user = this.userId; // userId is undefined
  return Posts.find({ _id, user });
});
// test.js
const collector = new PublicationCollector({ userId });
collector.collect('posts', (collections) => {
  assert.equal(collections.posts.length, 5);
  done();
});

publication returns [] but collection is undefined

Hi,

I have a publication that returns a cursor. If the collection exists but the cursor has no documents, I would expect publication-collector have an empty array for that collection. Instead it is undefined.

Error: Cannot read property 'length' of undefined

If I add one document its fine.

Is this behavior expected?

Here's my publication:

Meteor.publish("pairsAsParticipant", function (participantId) {
  authorizeOwner.call(this, participantId);
  return Pairs.find( { $and:   [ {"inviteeId":{$ne:null}},  { $or: [ { inviterId: participantId }, { inviteeId: participantId } ] } ]  } );
});

and my test:

  describe('No Pairs', () => {
    beforeEach(function (){
      resetDatabase()
      // make 5 pairs
      const fiveRandomPairs = [...Array(5)].map( () => {
        return Factory.create('pair', { inviterId: Random.id(), inviteeId: Random.id()  })
      })
    })

    it('should find zero pairs if currentUser is not a participant', function() {
      const collector = new PublicationCollector({userId: currentUserId})
      collector.collect('pairsAsParticipant', currentUserId,  (collections) => {
        expect(collections.pairs).to.have.lengthOf(0)
      })
    })
  })

Behaviour onStop

Since 1.0.4 the behaviour of collect(...) regarding this.stop() seems to be changed,
the callback will not be called if the publication is stopped before it is "ready".
Is this intended?

Thanks

Needs license

Could you please add a license so that others can use this package? Publishing software without a license means that anyone attempting to use it would be in violation of copyright. If you could please add an MIT or another distribution-friendly license, that would be appreciated!

Thanks in advance!

Error when using findOne() in publication

When using a findOne() query like so:

Meteor.publish('event', (id) => {
    return Events.findOne({_id: id})
})

I get an error when I try to call collect() on a publication collector:

Error: Object [object Object] has no method '_publishCursor'
    at packages/johanbrook:publication-collector/publication-collector.js:33:44

I realize findOne() doesn't return a cursor, but a document. For now I can use a regular find() query in the publication as a temporary workaround. Any suggestions on how to fix this issue?

Not working with `peerlibrary:reactive-publish`

I can't use publication-collector while using peerlibrary:reactive-publish at the same time.

server/example.test.js

import { PublicationCollector } from 'meteor/johanbrook:publication-collector'
import '/imports/api/server/publications.js'

describe('example', () => {
	it('should return empty array', function(done) {
		const collector = new PublicationCollector()

		collector.collect('publication', (collections) => {
			expect(collections.publication).to.be.deep.equal([])
			done()
		})
	})
})

server/publications.js

Meteor.publish('publication', function() {
	this.autorun(function() {
		return Collection.find({ any: 'thing' })
	})
})

I have to remove this.autorun() to get the test working. Otherwise I'll get this error:

Error: publish._isDeactivated is not a function
at Computation.<anonymous> (packages/peerlibrary_reactive-publish.js:349:24)
at runWithEnvironment (packages/meteor.js:1176:24)
at packages/meteor.js:1189:14
at packages/peerlibrary_server-autorun.js:469:23
at packages/peerlibrary_server-autorun.js:447:18
at Function.FiberUtils.synchronize (packages/peerlibrary_fiber-utils.js:183:12)
at Computation.Tracker.Computation.Computation._runInside (packages/peerlibrary_server-autorun.js:436:23)
at packages/peerlibrary_server-autorun.js:461:22
at Function.FiberUtils.synchronize (packages/peerlibrary_fiber-utils.js:183:12)
at Computation.Tracker.Computation.Computation._compute (packages/peerlibrary_server-autorun.js:458:23)

Using:

eslint setup

I did npm i, is there anything else I need to do to set up eslint? My editor (atom) is giving me Error: Cannot find module 'eslint-config-lookback/meteor'

Can't use on publication of users

I have a publication that publishes a selection of users and fields depending on the user, but it doesn't work. I get the output below when i try to run it.

TypeError: Cannot read property 'call' of undefined
at PublicationCollector.collect (packages/johanbrook:publication-collector/publication-collector.js:67:28)

My publication looks like this:

Meteor.publish('users', function () {
  const user = Meteor.users.findOne(this.userId);
  if (!user) {
    return this.ready();
  }

  if (Roles.userIsInRole(user._id, ['admin'])) {
    return Meteor.users.find({}, {
      fields: {
        createdAt: 1,
        emails: 1,
        roles: 1,
      },
    });
  }
  return this.ready();
});

I have emptied and populated both the user and role collection and trying just to get the test working like this:

  it('should publish users', function(done) {
    const collector = new PublicationCollector({ userId: 'D9E6CRGLX4Me3p6x9' });
    collector.collect('users', (collections) => {

      done();
    });
  });

Support multiple `collector.collect`-calls within 1 test

Hi Johan,

first of all: thanks for this cool package!! ๐Ÿ‘

I have a useCase where I am creating shitloads of fixtures (they take about 10 seconds to be created) and I'd love to write ONE integrational-test, testing different subscriptions - all within ONE test.

The problem is that collecter.collect can only be run ONCE within the testcase.

It would be so cool if we could use the syntax in synchronous style, so that we can call collecter.collect multiple times, like so:

  it('should allow to call collect multiple times', function() {
    const collector = new PublicationCollector({userId: Random.id()});

    collector.collect('publicationName', 'arg1', (collections) => {
      console.log('arg1')
      assert.equal(collections.myCollection.length, 10);
    });
    collector.collect('publicationName', 'arg2', (collections) => {
      // NOT called right now
      console.log('arg2')
      assert.equal(collections.myCollection.length, 10);
    });
  });

or

  it('should allow to call collect multiple times', function(done) {
    const collector = new PublicationCollector({userId: Random.id()});

    const differentArgumentValues = [
      'argValue1',
      'argValue2',
      'argValue3',
    ]
    _.each(differentArgumentValues, (currentArgument) => {
      collector.collect('publicationName', currentArgument, (collections) => {
        console.log(`testing currentArgument "${currentArgument}"`)  // only gets called for the first value "argValue1"
        assert.equal(collections.myCollection.length, 10);
      });
    })
    done();
  });

Any chance or trick of supporting this?

Use universal_publish_handlers when no publication-name is given

I want to test (server side) a package, that auto-publishes data to the user. The publication scheme it uses therefore is

Metepr.publish(null, function(){...})

which adds the publication to the universal_publish_handlers instead of publish_handlers, as taken from the code.

When calling the collector.collect(null, ...); then of course I get an error, because it uses publish_handlers[name] and not universal_publish_handlers when no name is provided.

Does it make sense to add this feature? I could try to provide a PR with an implementation and some tests, if desired.

Does this work with peerlibrary:reactive-publish

?
This is what we get in our terminal as the error:

Exception in queued task: TypeError: Cannot call method 'idStringify' of undefined
at PublicationCollector.publish.added (packages/peerlibrary_reactive-publish/packages/peerlibrary_reactive-publish.js:283:1)
at added (packages/mongo/collection.js:346:11)
at packages/mongo/observe_multiplex.js:183:30
at Function..each..forEach (packages/underscore/underscore.js:108:1)
at Object.task (packages/mongo/observe_multiplex.js:177:9)
at [object Object]._.extend._run (packages/meteor/fiber_helpers.js:147:1)
at packages/meteor/fiber_helpers.js:125:1

Package doesn't handle limit/skip correctly

In the latest version of Meteor (1.7.x) the following error is thrown when collecting data from a publication published with limit and/or skip:

Error: Must use an ordered observe with skip or limit (i.e. 'addedBefore' for observeChanges or 'addedAt' for observe, instead of 'added').

This seems to originate within the package because the same publication works correctly outside of the test.

"ready" event sent twice for publications using the low-level added/changed/removed interface

Hi,

First thanks for your package, it's really useful for testing Meteor publications.

With latest version I am getting following errors while testing my publications:
Error: done() called multiple times

Here the "done" function is the Mocha callback used for asynchronous code like the one you use in your usage example.

The publications I am testing are using the low-level added/changed/removed interface which means I am also calling the "ready" method once the initial record set is complete.
The problem in that case is that you're also calling "this.ready()" at the end of "collect" method and thus the callback passed to the "collect" method is called twice since it's part of the "ready" listener you're defining before calling the publication function.

That behavior was introduced in v1.0.5 since you're now adding the "ready" listener before calling the publication function.

I could be wrong but my recommendation would be to define an empty "ready" method on PublicationCollector class (like the stop() one) and use a different name for your custom ready event.

Let me know if that's not clear enough.
Thanks for your help.

-Nicolas

Breaking with peerlibrary:reactive-publish's "autorun"

I have run into a problem with peerlibrary:reactive-publish where "publish._isDeactivated" is not a function.

TypeError: publish._isDeactivated is not a function
       at Computation.<anonymous> (packages/peerlibrary_reactive-publish.js:354:24)
       at runWithEnvironment (packages/meteor.js:1176:24)
       at packages/meteor.js:1189:14
       at packages/peerlibrary_server-autorun.js:474:23
       at packages/peerlibrary_server-autorun.js:452:18
       at Function.FiberUtils.synchronize (packages/peerlibrary_fiber-utils.js:188:12)
       at Computation.Tracker.Computation.Computation._runInside (packages/peerlibrary_server-autorun.js:441:23)
       at packages/peerlibrary_server-autorun.js:466:22
       at Function.FiberUtils.synchronize (packages/peerlibrary_fiber-utils.js:188:12)
       at Computation.Tracker.Computation.Computation._compute (packages/peerlibrary_server-autorun.js:463:23)
       at new Computation (packages/peerlibrary_server-autorun.js:335:12)
       at Object.Tracker.autorun (packages/peerlibrary_server-autorun.js:253:7)
       at PublicationCollector.publish.autorun (packages/peerlibrary_reactive-publish.js:340:24)
       at imports/api/users/server/publications.js:52:22
       at PublicationCollector.<anonymous> (imports/api/users/server/publications.js:29:22)
       at PublicationCollector.<anonymous> (packages/peerlibrary_subscription-data.js:234:30)
       at PublicationCollector.<anonymous> (packages/peerlibrary_reactive-publish.js:371:30)
       at PublicationCollector.collect (packages/johanbrook:publication-collector/publication-collector.js:44:20)
       at Test.<anonymous> (imports/api/users/server/publicatons.test.js:35:19)

I wonder if this is along the same lines as the last issue that related to peerlibrary:reactive-publish?

Inconsistent results when checking length of returned collection

My collection that is being returned sometimes has only two items in the array, but more than often it has three. Here is my code

import { PublicationCollector } from 'meteor/johanbrook:publication-collector';
import { chai, assert } from 'meteor/practicalmeteor:chai';
import { Communities } from '/imports/collections/communities.jsx'
import { Random } from 'meteor/random';
import { resetDatabase } from 'meteor/xolvio:cleaner';
import { _ } from 'meteor/stevezhu:lodash';

if (Meteor.isServer) {
  describe('communities', function () {
    before( () => {
      resetDatabase()

      _.times(3, () => {
         Factory.create("community");
      });
  })

    after( () => {
      resetDatabase()
    })

    it('publishes all communities', function (done) {
      const collector = new PublicationCollector();

       collector.collect('communities', (collections) => {
         console.log("\n\nCOMMUNITIES BEFORE\n\n")
         console.log(Communities.find().fetch() )
         console.log("\n\nCOMMUNITIES AFTER\n\n")
         console.log(collections)

         chai.assert.typeOf(collections.communities, 'array');
         chai.assert.equal(collections.communities.length, 3);
         done();
        });
    });
  });
};

Usually this passing fine, but about 1/5 times it returns with an error saying expected 3 items in array, got 2.

screenshot 2016-06-29 18 40 24

Refreshing once or twice, I then get the correct results

screenshot 2016-06-29 18 41 51

Here is my console logs of the incorrect results

screenshot 2016-06-29 18 45 19

And here is the console log of the correct results

screenshot 2016-06-29 18 48 33

So it seems like sometimes, collections.communities is just returning only two, since a straight database calls shows all three are actually in there. Any Idea?

Error: Cannot read property 'call' of undefined

Hey Johan,

I recently came across a very missleading error from this package

Error: Cannot read property 'call' of undefined
    at PublicationCollector.collect (packages/johanbrook:publication-collector/publication-collector.js:67:28)
    ....

which refers to those 2 lines:

collect(name, ...args) {
  ...
  
  const handler = Meteor.server.publish_handlers[name];
  const result = handler.call(this, ...args);

  ...
}

It turns out I "just" misspelled my publication which apperantly caused the read error.

Due to the thrown error, it took me a little while to figure out what caused the error (also because I'm not familiar with publish_handlers or meteor internals in generall). Therefor I would love to get a more specific error message suggesting that I eventually misspelled the publication.

Unfortunately, I'm busy right now and therefor not able to provide a pull request. Sorry!

Error on stop with meteor-publish-performant-counts

Hello and thank you for this package!

I get an error on line 37:

this.observeHandles.forEach(handle => handle.stop());
โ€ฃ
Error: Cannot read property 'stop' of undefined
    at packages/johanbrook:publication-collector/publication-collector.js:37:47
    at Array.forEach (native)
    at PublicationCollector.<anonymous> (packages/johanbrook:publication-collector/publication-collector.js:37:29)
    at PublicationCollector.ready (packages/johanbrook:publication-collector/publication-collector.js:99:10)
    at PublicationCollector.collect (packages/johanbrook:publication-collector/publication-collector.js:53:10)
    at Test.<anonymous> (imports/projects/api/projects.tests.js:26:19)
    at run (packages/practicalmeteor:mocha-core/server.js:34:29)

The issue is caused by publishing a counter obtained using the meteor-publish-performant-counts package. This is my publication code:

Meteor.publish('PubName', function() {
    const cursor = collection.find({})
    const counter = new Counter('PubNameListCount', cursor)
    return [
      cursor, counter
    ];
})

I am not sure if this is because that package is not confirming to the subscription API, or because the publication-collector is relying on features that are not present in all publications. I did a little bit of investigation and I feel like it is the latter, but I am not that intimately familiar with the Meteor pub / sub mechanism yet so I could be missing something! Would there be interest in accepting a PR if it is warranted?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.