collection/item.js

var _ = require('../util').lodash,
    Property = require('./property').Property,
    PropertyList = require('./property-list').PropertyList,
    EventList = require('./event-list').EventList,
    Request = require('./request').Request,
    RequestAuth = require('./request-auth').RequestAuth,
    Response = require('./response').Response,

    Item,

    /**
     * @private
     * @type {String}
     */
    OBJECT = 'object',

    /**
     * @private
     * @type {String}
     */
    STRING = 'string',

    /**
     * Extracts `auth` from an entity. Checks if `auth` is present and it is not falsy type.
     *
     * @private
     *
     * @param {Object} [entity] -
     */
    extractAuth = function (entity) {
        var auth;

        return (entity && (auth = entity.auth) && RequestAuth.isValidType(auth.type)) ? auth : undefined;
    },

    /**
     * Extracts `protocolProfileBehavior` from an entity.
     *
     * @private
     *
     * @param {Item|ItemGroup} entity -
     * @returns {Object}
     */
    extractProtocolProfileBehavior = function (entity) {
        var protocolProfileBehavior = entity && entity.protocolProfileBehavior;

        return typeof protocolProfileBehavior === OBJECT ? protocolProfileBehavior : {};
    };

/**
 * The following defines the object (or JSON) structure that one can pass to the Item while creating a new Item
 * instance. This is also the object structure returned when `.toJSON()` is called on an Item instance.
 *
 * @typedef Item.definition
 *
 * @property {Request.definition=} [request] A request represents an HTTP request. If a string, the string is assumed to
 * be the request URL and the method is assumed to be 'GET'.
 * @property {Array<Response.definition>=} [responses] Sample responses for this request can be stored along with the
 * item definition.
 * @property {Array<Event.definition>=} [events] Postman allows you to configure scripts to run when specific events
 * occur. These scripts are stored here, and can be referenced in the collection by their id.
 *
 * @example
 * {
 *     "name": "Get Headers from Echo",
 *     "id": "my-request-1",
 *     "description": "Makes a GET call to echo service and returns the client headers that were sent",
 *
 *     "request": {
 *         "url": "https://postman-echo.com/headers",
 *         "method": "GET"
 *     }
 * }
 *
 * @todo add response and event to example
 */
_.inherit((

    /**
     * A Postman Collection Item that holds your request definition, responses and other stuff. An Item essentially is
     * a HTTP request definition along with the sample responses and test scripts clubbed together. One or more of these
     * items can be grouped together and placed in an {@link ItemGroup} and as such forms a {@link Collection} of
     * requests.
     *
     * @constructor
     * @extends {Property}
     *
     * @param {Item.definition=} [definition] While creating a new instance of Item, one can provide the initial
     * configuration of the item with the the request it sends, the expected sample responses, tests, etc
     *
     * @example <caption>Add a new Item to a folder in a collection instance</caption>
     * var Collection = require('postman-collection').Collection,
     *     Item = require('postman-collection').Item,
     *     myCollection;
     *
     * myCollection = new Collection({
     *     "item": [{
     *         "id": "my-folder-1",
     *         "name": "The solo folder in this collection",
     *         "item": [] // blank array indicates this is a folder
     *     }]
     * }); // create a collection with an empty folder
     * // add a request to "my-folder-1" that sends a GET request
     * myCollection.items.one("my-folder-1").items.add(new Item({
     *     "name": "Send a GET request",
     *     "id": "my-get-request",
     *     "request": {
     *         "url": "https://postman-echo.com/get",
     *         "method": "GET"
     *     }
     * }));
     */
    Item = function PostmanItem (definition) {
        // this constructor is intended to inherit and as such the super constructor is required to be executed
        Item.super_.apply(this, arguments);

        _.mergeDefined(this, /** @lends Item.prototype */ {
            /**
             * The instance of the {@link Request} object inside an Item defines the HTTP request that is supposed to be
             * sent. It further contains the request method, url, request body, etc.
             *
             * @type {Request}
             */
            request: definition && (new Request(definition.request)),

            /**
             * An Item also contains a list of sample responses that is expected when the request defined in the item is
             * executed. The sample responses are useful in elaborating API usage and is also useful for other
             * integrations that use the sample responses to do something - say a mock service.
             *
             * @type {PropertyList<Response>}
             */
            responses: new PropertyList(Response, this, definition && definition.response),

            /**
             * Events are a set of of {@link Script}s that are executed when certain activities are triggered on an
             * Item. For example, on defining an event that listens to the "test" event, would cause the associated
             * script of the event to be executed when the test runs.
             *
             * @type {EventList}
             *
             * @example <caption>Add a script to be executed on "prerequest" event</caption>
             * var Collection = require('postman-collection').Collection,
             *     Item = require('postman-collection').Item,
             *     myCollection;
             *
             * myCollection = new Collection({
             *     "item": [{
             *         "name": "Send a GET request",
             *         "id": "my-get-request",
             *         "request": {
             *             "url": "https://postman-echo.com/get",
             *             "method": "GET"
             *         }
             *     }]
             * }); // create a collection with one request
             *
             * // add a pre-request script to the event list
             * myCollection.items.one('my-get-request').events.add({
             *     "listen": "prerequest",
             *     "script": {
             *         "type": "text/javascript",
             *         "exec": "console.log(new Date())"
             *     }
             * });
             */
            events: new EventList(this, definition && definition.event),

            /**
             * Set of configurations used to alter the usual behavior of sending the request.
             *
             * @type {Object}
             */
            protocolProfileBehavior: definition && typeof definition.protocolProfileBehavior === OBJECT ?
                definition.protocolProfileBehavior : undefined
        });
    }), Property);

_.assign(Item.prototype, /** @lends Item.prototype */ {
    /**
     * Defines whether this property instances requires an id
     *
     * @private
     * @readOnly
     * @type {Boolean}
     */
    _postman_propertyRequiresId: true,

    /**
     * Fetches applicable AuthType from the current item.
     *
     * @returns {RequestAuth}
     *
     * @note Since v3.0 release, this returns the entire auth RequestAuth, instead of just the parameters
     *
     * @todo Deprecate this and use getAuthResolved instead
     */
    getAuth: function () {
        var requestAuth;

        // find auth on request, if not found or empty auth, lookup in the parents
        // eslint-disable-next-line no-cond-assign
        return (requestAuth = extractAuth(this.request)) ? requestAuth : this.findInParents('auth', extractAuth);
    },

    /**
     * Fetches protocol profile behavior for the current Item
     *
     * @private
     * @returns {Object}
     *
     * @note This will not inherit protocol profile behaviors from parent,
     * use `getProtocolProfileBehaviorResolved` to achieve that behavior.
     */
    getProtocolProfileBehavior: function () {
        return extractProtocolProfileBehavior(this);
    },

    /**
     * Fetches protocol profile behavior applicable for the current Item,
     * inherited from parent ItemGroup(s).
     *
     * @private
     * @returns {Object}
     */
    getProtocolProfileBehaviorResolved: function () {
        var protocolProfileBehavior = extractProtocolProfileBehavior(this);

        // inherit protocolProfileBehavior from ItemGroup(s)
        this.forEachParent({ withRoot: true }, function (entity) {
            protocolProfileBehavior = {

                ...extractProtocolProfileBehavior(entity),
                ...protocolProfileBehavior
            };
        });

        return protocolProfileBehavior;
    },

    /**
     * Set or update protocol profile behavior for the current Item.
     *
     * @example <caption> Set or update protocol profile behavior </caption>
     * item.setProtocolProfileBehavior('strictSSL', false);
     *
     * @private
     * @param {String} key - protocol profile behavior name
     * @param {*} value - protocol profile behavior value
     * @returns {Item}
     */
    setProtocolProfileBehavior: function (key, value) {
        // bail out if key is non-string
        if (typeof key !== STRING) { return this; }

        !this.protocolProfileBehavior && (this.protocolProfileBehavior = {});

        this.protocolProfileBehavior[key] = value;

        return this;
    },

    /**
     * Unset or delete protocol profile behavior for the current Item.
     *
     * @example <caption> Unset protocol profile behavior </caption>
     * item.unsetProtocolProfileBehavior('strictSSL');
     *
     * @private
     * @param {String} key - protocol profile behavior name to unset
     * @returns {Item}
     */
    unsetProtocolProfileBehavior: function (key) {
        // bail out if property protocolProfileBehavior is not set or key is non-string
        if (!(typeof this.protocolProfileBehavior === OBJECT && typeof key === STRING)) {
            return this;
        }

        if (_.has(this.protocolProfileBehavior, key)) {
            delete this.protocolProfileBehavior[key];
        }

        return this;
    },

    /**
     * Returns {@link Event}s corresponding to a particular event name. If no name is given, returns all events. This
     * is useful when you want to trigger all associated scripts for an event.
     *
     * @param {String} name - one of the available event types such as `test`, `prerequest`, `postrequest`, etc.
     * @returns {Array<Event>}
     *
     * @example <caption>Get all events for an item and evaluate their scripts</caption>
     * var fs = require('fs'), // needed to read JSON file from disk
     *     Collection = require('postman-collection').Collection,
     *     myCollection;
     *
     * // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
     * myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
     *
     * // assuming the collection has a request called "my-request-1" in root, we get it's test events
     * myCollection.items.one("my-request-1").getEvents("test").forEach(function (event) {
     *     event.script && eval(event.script.toSource());
     * });
     *
     * @todo decide appropriate verb names based on the fact that it gets events for a specific listener name
     * @draft
     */
    getEvents: function (name) {
        if (!name) {
            return this.events.all(); // return all events if name is not provided.
        }

        return this.events.filter(function (ev) {
            return ev.listen === name;
        });
    },

    /**
     * Sets authentication method for the request within this item
     *
     * @param {?String|RequestAuth.definition} type -
     * @param {VariableList=} [options] -
     *
     * @note This function was previously (in v2 of SDK) used to clone request and populate headers. Now it is used to
     * only set auth information to request
     */
    authorizeRequestUsing: function (type, options) {
        if (!this.request) { this.request = new Request(); } // worst case

        return this.request.authorizeUsing(type, options);
    }
});

_.assign(Item, /** @lends Item */ {
    /**
     * Defines the name of this property for internal use.
     *
     * @private
     * @readOnly
     * @type {String}
     */
    _postman_propertyName: 'Item',

    /**
     * Check whether an object is an instance of PostmanItem.
     *
     * @param {*} obj -
     * @returns {Boolean}
     */
    isItem: function (obj) {
        return Boolean(obj) && ((obj instanceof Item) ||
            _.inSuperChain(obj.constructor, '_postman_propertyName', Item._postman_propertyName));
    }
});

module.exports = {
    Item
};