[solved] Instance of Meteor.publish() is Object while testing

Hello, I’m trying to make publication integration tests and faced with situation when inside one publications this instanceof === Object, I mean it is global object, not Publications. Why could this happens?

code of test:

it("should not let a regular user fetch userIds", function (done) {
  const shopMembers = Meteor.server.publish_handlers["ShopMembers"];
  spyOn(Meteor.server.publish_handlers, "ShopMembers").and.callFake(
    function () {
      this.userId = "notAdminUser";
      return shopMembers.apply(this, arguments);
    }
  );
  const cursor = Meteor.server.publish_handlers.ShopMembers();
  expect(cursor).toEqual([]);

  return done();
});

can you post your publication code as well?

@sam, Hello. Thanks for reply. Sure.

Meteor.publish("ShopMembers", function () {
  // here we are comparing with the string to make it compatible with tests
  if (typeof this.userId !== "string") {
    return this.ready();
  }
  let permissions = ["dashboard/orders", "owner", "admin", "dashboard/customers"];
  let shopId = ReactionCore.getShopId();

  if (Roles.userIsInRole(this.userId, permissions, shopId)) {
    // seems like we can't use "`" inside db.call directly
    const rolesShopId = `roles.${shopId}`;
    return Meteor.users.find({
      rolesShopId: {
        $nin: ["anonymous"]
      }
    }, {
      fields: {
        _id: 1,
        email: 1,
        username: 1,
        roles: 1,

        // Google
        "services.google.name": 1,
        "services.google.email": 1,
        "services.google.picture": 1,

        // Twitter
        "services.twitter.name": 1,
        "services.twitter.email": 1,
        "services.twitter.profile_image_url_https": 1,

        // Facebook
        "services.facebook.name": 1,
        "services.facebook.email": 1,
        "services.facebook.id": 1,

        // Weibo
        "services.weibo.name": 1,
        "services.weibo.email": 1,
        "services.weibo.picture": 1,

        // Github
        "services.github.name": 1,
        "services.github.email": 1,
        "services.github.username": 1
      }
    });
  }

  ReactionCore.Log.debug("ShopMembers access denied");
  return this.ready();
});

Also, the test in previous post is not complete. I cut out some spyOns to make the picture simpler.

this test should work:

it("should not let a regular user fetch userIds", function () {
  const publication = Meteor.server.publish_handlers["ShopMembers"];
  const thisContext = {
    userId: 'notAdminUser'
  };
  // spy on what you need to spy on here to make sure you get to the correct code path you want to test

  const cursor = publication.apply(thisContext);

  expect(cursor).toEqual([]);
});

@sam, thanks again, but my problem is not only in test, but why Meteor.publish this instanceof Object? How do you think, is this publication code correct?

Btw, I’m successfully running this test like so:

it("should not let a regular user fetch userIds", function (done) {
  // setup
  spyOn(Meteor.server.publish_handlers, "ShopMembers").and.callFake(
    function () {
      this.userId = "notAdminUserId";
      // not so good, but this is the best I found to make this test works
      this.ready = new Function("", "return 'test passed';");
      return shopMembers.apply(this, arguments);
    }
  );
  spyOn(ReactionCore, "getShopId").and.returnValue(shopId);
  spyOn(Roles, "userIsInRole").and.returnValue(false);
  // fixme: unexpected situation here. `this` inside this publication
  // instance of global object, not `Publication`
  const cursor = Meteor.server.publish_handlers.ShopMembers();
  expect(cursor).toEqual("test passed");

  done();
});

I’m not sure I understand what the error is then!

@sam, I’ve tried you test. It throw an error as expected: Object [object Object] has no method 'ready'.

Can you show me where you are checking for typeof, and what that is without a test, and what it in a test? that will help me understand

While test running I’m make a breakpoint in publication.
In test it is: this instanceof Object
in real: this instanceof Publication

Ah I get what you mean. It’s because you are using apply, and passing in a new this context, which is an object. When Meteor calls it in the “real world” it gives it a Publication object.

This is a good thing, as it allows you to control the context of the publication. All you need to do now is this:

it("should not let a regular user fetch userIds", function () {
  const publication = Meteor.server.publish_handlers["ShopMembers"];
  const thisContext = {
    userId: 'notAdminUser',
    ready: function() { return true; }
  };
  spyOn(ReactionCore, "getShopId").and.returnValue(shopId);
  spyOn(Roles, "userIsInRole").and.returnValue(false);

  const cursor = publication.apply(thisContext);

  expect(cursor).toEqual([]);
});

You don’t need the extra callFake if you do it the way I suggest here. Also, you don’t need a done since this test is not async

Great, thanks, now your test passes fine and I discovered two new things :blush: