<commit_before>/*global it:true, describe:true */
var assert = require("assert");
var parseMetar = require("../metar");

describe("METAR parser", function() {

    it("can parse station", function(){
        var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
        assert.equal("EFJY", m.station);
    });

    it("can parse time of observation", function(){
        var m = parseMetar("EFJY 181750Z AUTO 29007KT CAVOK 15/12 Q1006");
        assert.equal(18, m.time.getUTCDate());
        assert.equal(17, m.time.getUTCHours());
        assert.equal(50, m.time.getUTCMinutes());
    });

    it("can parse auto", function(){
        var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
        assert.equal(true, m.auto);
    });

    it("can parse metar without auto", function() {
        var m = parseMetar("EFJY 171750Z 29007KT CAVOK 15/12 Q1006");
        assert.equal(290, m.wind.direction);
        assert(!m.auto);
    });

    it("can parse CAVOK", function(){
        var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
        assert.equal(true, m.cavok);
    });

    describe("for winds", function() {
        it("can parse direction", function(){
            var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
            assert.equal(290, m.wind.direction);
        });
        it("can parse speed", function(){
            var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
            assert.equal(7, m.wind.speed);
            assert.equal("KT", m.wind.unit);
        });
        it("can parse variable directions", function(){
            var m = parseMetar("EFHF 171820Z AUTO 29007KT 240V330 CAVOK 15/11 Q1010");
            assert.deepEqual({min: 240, max: 330}, m.wind.variation);
        });
        it("can parse small variable directions", function(){
            var m = parseMetar("EFVA 171850Z AUTO VRB02KT CAVOK 15/11 Q1008");
            assert.equal(2, m.wind.speed);
            assert.equal(true, m.wind.variation);
            assert.equal("VRB", m.wind.direction);
        });
        it("can parse gusts", function(){
            var m = parseMetar("EFVA 171850Z AUTO 24028G42KT CAVOK 15/11 Q1008");
            assert.equal(28, m.wind.speed);
            assert(!m.wind.variation);
            assert.equal(240, m.wind.direction);
            assert.equal(42, m.wind.gust);
        });

        it("can MPH speed", function() {
            var m = parseMetar("ULLI 172030Z 23004MPS 9999 -SHRA SCT022CB BKN043 OVC066 13/10 Q1010 NOSIG");
            assert.equal(4, m.wind.speed);
            assert.equal("MPS", m.wind.unit);
        });
    });

    describe("for visibility", function() {
        it("parses no visibility for CAVOK", function(){
            var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
            assert(!m.visibility);
        });
        it("can parse visibility", function(){
            var m = parseMetar("EFET 171920Z AUTO 04007KT 010V070 9999 OVC035 09/05 Q1009");
            assert.equal(9999, m.visibility);
        });
        it("can skip missing visibility", function(){
            var m = parseMetar("EFHF 172050Z AUTO 26003KT //// SKC 13/10 Q1012");
            assert.equal(null, m.visibility);
            assert.deepEqual([{
                abbreviation: "SKC",
                meaning: "sky clear",
                cumulonimbus: false,
                altitude: null
            }], m.clouds);
        });
    });

    describe("for weather conditions", function() {
        it("can parse it", function() {
            var m = parseMetar("EFKI 171950Z 00000KT 9999 MIFG FEW012 SCT200 10/10 Q1006");
            assert.deepEqual([{"abbreviation":"MI","meaning":"shallow"},{"abbreviation":"FG","meaning":"fog"}], m.weather);
        });
        it("can parse single attribute weather", function() {
            var m = parseMetar("EFKI 172020Z AUTO 00000KT 2600 BR SKC 09/09 Q1006");
            assert.deepEqual([{"abbreviation":"BR","meaning":"mist"}], m.weather);
        });
        it("can parse three attribute weather", function() {
            var m = parseMetar("ULLI 172030Z 23004MPS 9999 -SHRA SCT022CB BKN043 OVC066 13/10 Q1010 NOSIG");
            assert.deepEqual([
                {"abbreviation":"-","meaning":"light intensity"},
                {"abbreviation":"SH","meaning":"showers"},
                {"abbreviation":"RA","meaning":"rain"}
                ],m.weather);
        });

        it("can parse multiple weather conditions", function() {
            var m = parseMetar("EFJY 092120Z AUTO 05003KT 9999 -SHRA VCSH SCT006 OVC028CB 13/13 Q1014");
            assert.deepEqual([
                 { abbreviation: '-', meaning: 'light intensity' },
                 { abbreviation: 'SH', meaning: 'showers' },
                 { abbreviation: 'RA', meaning: 'rain' },
                 { abbreviation: 'VC', meaning: 'in the vicinity' },
                 { abbreviation: 'SH', meaning: 'showers' }
            ], m.weather);

            assert.deepEqual(m.clouds.length, 2);
        });
    });

    describe("for clouds", function() {
        it("can parse single cloud level", function(){
            var m = parseMetar("EFET 171920Z AUTO 04007KT 010V070 9999 OVC035 09/05 Q1009");
            assert.deepEqual([{
                abbreviation: "OVC",
                meaning: "overcast",
                cumulonimbus: false,
                altitude: 3500
            }], m.clouds);
        });
        it("can parse multiple cloud levels", function(){
            var m = parseMetar("EFVR 171950Z AUTO 27006KT 220V310 9999 FEW012 SCT015 BKN060 13/12 Q1006");
            assert.deepEqual({
                abbreviation: "FEW",
                meaning: "few",
                cumulonimbus: false,
                altitude: 1200
            }, m.clouds[0]);
            assert.deepEqual({
                abbreviation: "SCT",
                meaning: "scattered",
                cumulonimbus: false,
                altitude: 1500
            }, m.clouds[1]);
            assert.deepEqual({
                abbreviation: "BKN",
                meaning: "broken",
                cumulonimbus: false,
                altitude: 6000
            }, m.clouds[2]);
        });

        it("runway visibility does not break cloud parsing", function() {
            var m = parseMetar("EFJY 082120Z AUTO 00000KT 9999 R30/1300U BKN083 BKN101 15/12 Q1013");
            assert(m.clouds);
        });

        it("can parse without altitude", function() {
            var m = parseMetar("EFKI 172020Z AUTO 00000KT 2600 BR SKC 09/09 Q1006");
            assert.deepEqual([{
                abbreviation: "SKC",
                altitude: null,
                cumulonimbus: false,
                meaning: "sky clear"
            }], m.clouds);

            assert.equal(2600, m.visibility);

        });

        it("can parse NCD (no clouds)", function(){
            var m = parseMetar("EFKA 181750Z AUTO 30007KT //// NCD 16/04 Q1015");
            assert.equal(null, m.visibility);
            assert.deepEqual([{
                abbreviation:"NCD",
                altitude: null,
                cumulonimbus: false,
                meaning: "no clouds"
            }], m.clouds);
        });

        it("can parse NSC (no significant clouds)", function(){
            var m = parseMetar("EFKA 181750Z AUTO 30007KT //// NSC 16/04 Q1015");
            assert.equal(null, m.visibility);
            assert.deepEqual([{
                abbreviation:"NSC",
                altitude: null,
                cumulonimbus: false,
                meaning:"no significant"
            }], m.clouds);
        });

        it("can parse VV", function(){
            var m = parseMetar("EFVR 171950Z AUTO 27006KT 220V310 9999 VV060 13/12 Q1006");
            assert.deepEqual([{
                abbreviation:"VV",
                altitude: 6000,
                cumulonimbus: false,
                meaning:"vertical visibility"
            }], m.clouds);
        });

        it("can parse cumulonimbus", function(){
            var m = parseMetar("EFJY 201050Z AUTO 16007KT 9999 -SHRA OVC060CB 15/09 Q1017");
            assert.deepEqual([{
                abbreviation: "OVC",
                altitude: 6000,
                meaning: "overcast",
                cumulonimbus: true
            }], m.clouds);
        });

    });

    describe("for temp/dewpoint", function() {
        it("can parse it", function() {
            var m = parseMetar("EFKI 171950Z 00000KT 9999 MIFG FEW012 SCT200 10/11 Q1006");
                assert.equal(10, m.temperature);
                assert.equal(11, m.dewpoint);
        });

        it("can parse neg", function() {
            var m = parseMetar("KLZZ 302355Z AUTO 00000KT 10SM CLR 04/M02 A3029 RMK AO2 T00391018 10070 20031");
            assert.equal(4, m.temperature);
            assert.equal(-2, m.dewpoint);
        });

    });

    describe("for altimeter", function() {
        it("can parse inches mercury", function() {
            var m = parseMetar("EFKI 171950Z 00000KT 9999 MIFG FEW012 SCT200 10/11 Q1006");
            assert.equal(1006, m.altimeter_hpa);
        });

        it("can parse hpa", function() {
            var m = parseMetar("KLZZ 302355Z AUTO 00000KT 10SM CLR 04/M02 A3029 RMK AO2 T00391018 10070 20031");
            assert.equal(30.29, m.altimeter_in_hg);
        });

    });

});
<commit_msg>Tests for corrections and double negative temps<commit_after>/*global it:true, describe:true */
var assert = require("assert");
var parseMetar = require("../metar");

describe("METAR parser", function() {

    it("can parse station", function(){
        var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
        assert.equal("EFJY", m.station);
    });

    it("can parse time of observation", function(){
        var m = parseMetar("EFJY 181750Z AUTO 29007KT CAVOK 15/12 Q1006");
        assert.equal(18, m.time.getUTCDate());
        assert.equal(17, m.time.getUTCHours());
        assert.equal(50, m.time.getUTCMinutes());
    });

    it("can parse auto", function(){
        var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
        assert.equal(true, m.auto);
    });

    it("can parse correction", function() {
        var m = parseMetar("CYZF 241700Z CCA 32012G18KT 12SM BKN007 OVC042 M02/M05 A2956");
        assert.equal("A", m.correction);
    });

    it("can parse metar without auto", function() {
        var m = parseMetar("EFJY 171750Z 29007KT CAVOK 15/12 Q1006");
        assert.equal(290, m.wind.direction);
        assert(!m.auto);
    });

    it("can parse CAVOK", function(){
        var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
        assert.equal(true, m.cavok);
    });

    describe("for winds", function() {
        it("can parse direction", function(){
            var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
            assert.equal(290, m.wind.direction);
        });
        it("can parse speed", function(){
            var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
            assert.equal(7, m.wind.speed);
            assert.equal("KT", m.wind.unit);
        });
        it("can parse variable directions", function(){
            var m = parseMetar("EFHF 171820Z AUTO 29007KT 240V330 CAVOK 15/11 Q1010");
            assert.deepEqual({min: 240, max: 330}, m.wind.variation);
        });
        it("can parse small variable directions", function(){
            var m = parseMetar("EFVA 171850Z AUTO VRB02KT CAVOK 15/11 Q1008");
            assert.equal(2, m.wind.speed);
            assert.equal(true, m.wind.variation);
            assert.equal("VRB", m.wind.direction);
        });
        it("can parse gusts", function(){
            var m = parseMetar("EFVA 171850Z AUTO 24028G42KT CAVOK 15/11 Q1008");
            assert.equal(28, m.wind.speed);
            assert(!m.wind.variation);
            assert.equal(240, m.wind.direction);
            assert.equal(42, m.wind.gust);
        });

        it("can MPH speed", function() {
            var m = parseMetar("ULLI 172030Z 23004MPS 9999 -SHRA SCT022CB BKN043 OVC066 13/10 Q1010 NOSIG");
            assert.equal(4, m.wind.speed);
            assert.equal("MPS", m.wind.unit);
        });
    });

    describe("for visibility", function() {
        it("parses no visibility for CAVOK", function(){
            var m = parseMetar("EFJY 171750Z AUTO 29007KT CAVOK 15/12 Q1006");
            assert(!m.visibility);
        });
        it("can parse visibility", function(){
            var m = parseMetar("EFET 171920Z AUTO 04007KT 010V070 9999 OVC035 09/05 Q1009");
            assert.equal(9999, m.visibility);
        });
        it("can skip missing visibility", function(){
            var m = parseMetar("EFHF 172050Z AUTO 26003KT //// SKC 13/10 Q1012");
            assert.equal(null, m.visibility);
            assert.deepEqual([{
                abbreviation: "SKC",
                meaning: "sky clear",
                cumulonimbus: false,
                altitude: null
            }], m.clouds);
        });
    });

    describe("for weather conditions", function() {
        it("can parse it", function() {
            var m = parseMetar("EFKI 171950Z 00000KT 9999 MIFG FEW012 SCT200 10/10 Q1006");
            assert.deepEqual([{"abbreviation":"MI","meaning":"shallow"},{"abbreviation":"FG","meaning":"fog"}], m.weather);
        });
        it("can parse single attribute weather", function() {
            var m = parseMetar("EFKI 172020Z AUTO 00000KT 2600 BR SKC 09/09 Q1006");
            assert.deepEqual([{"abbreviation":"BR","meaning":"mist"}], m.weather);
        });
        it("can parse three attribute weather", function() {
            var m = parseMetar("ULLI 172030Z 23004MPS 9999 -SHRA SCT022CB BKN043 OVC066 13/10 Q1010 NOSIG");
            assert.deepEqual([
                {"abbreviation":"-","meaning":"light intensity"},
                {"abbreviation":"SH","meaning":"showers"},
                {"abbreviation":"RA","meaning":"rain"}
                ],m.weather);
        });

        it("can parse multiple weather conditions", function() {
            var m = parseMetar("EFJY 092120Z AUTO 05003KT 9999 -SHRA VCSH SCT006 OVC028CB 13/13 Q1014");
            assert.deepEqual([
                 { abbreviation: '-', meaning: 'light intensity' },
                 { abbreviation: 'SH', meaning: 'showers' },
                 { abbreviation: 'RA', meaning: 'rain' },
                 { abbreviation: 'VC', meaning: 'in the vicinity' },
                 { abbreviation: 'SH', meaning: 'showers' }
            ], m.weather);

            assert.deepEqual(m.clouds.length, 2);
        });
    });

    describe("for clouds", function() {
        it("can parse single cloud level", function(){
            var m = parseMetar("EFET 171920Z AUTO 04007KT 010V070 9999 OVC035 09/05 Q1009");
            assert.deepEqual([{
                abbreviation: "OVC",
                meaning: "overcast",
                cumulonimbus: false,
                altitude: 3500
            }], m.clouds);
        });
        it("can parse multiple cloud levels", function(){
            var m = parseMetar("EFVR 171950Z AUTO 27006KT 220V310 9999 FEW012 SCT015 BKN060 13/12 Q1006");
            assert.deepEqual({
                abbreviation: "FEW",
                meaning: "few",
                cumulonimbus: false,
                altitude: 1200
            }, m.clouds[0]);
            assert.deepEqual({
                abbreviation: "SCT",
                meaning: "scattered",
                cumulonimbus: false,
                altitude: 1500
            }, m.clouds[1]);
            assert.deepEqual({
                abbreviation: "BKN",
                meaning: "broken",
                cumulonimbus: false,
                altitude: 6000
            }, m.clouds[2]);
        });

        it("runway visibility does not break cloud parsing", function() {
            var m = parseMetar("EFJY 082120Z AUTO 00000KT 9999 R30/1300U BKN083 BKN101 15/12 Q1013");
            assert(m.clouds);
        });

        it("can parse without altitude", function() {
            var m = parseMetar("EFKI 172020Z AUTO 00000KT 2600 BR SKC 09/09 Q1006");
            assert.deepEqual([{
                abbreviation: "SKC",
                altitude: null,
                cumulonimbus: false,
                meaning: "sky clear"
            }], m.clouds);

            assert.equal(2600, m.visibility);

        });

        it("can parse NCD (no clouds)", function(){
            var m = parseMetar("EFKA 181750Z AUTO 30007KT //// NCD 16/04 Q1015");
            assert.equal(null, m.visibility);
            assert.deepEqual([{
                abbreviation:"NCD",
                altitude: null,
                cumulonimbus: false,
                meaning: "no clouds"
            }], m.clouds);
        });

        it("can parse NSC (no significant clouds)", function(){
            var m = parseMetar("EFKA 181750Z AUTO 30007KT //// NSC 16/04 Q1015");
            assert.equal(null, m.visibility);
            assert.deepEqual([{
                abbreviation:"NSC",
                altitude: null,
                cumulonimbus: false,
                meaning:"no significant"
            }], m.clouds);
        });

        it("can parse VV", function(){
            var m = parseMetar("EFVR 171950Z AUTO 27006KT 220V310 9999 VV060 13/12 Q1006");
            assert.deepEqual([{
                abbreviation:"VV",
                altitude: 6000,
                cumulonimbus: false,
                meaning:"vertical visibility"
            }], m.clouds);
        });

        it("can parse cumulonimbus", function(){
            var m = parseMetar("EFJY 201050Z AUTO 16007KT 9999 -SHRA OVC060CB 15/09 Q1017");
            assert.deepEqual([{
                abbreviation: "OVC",
                altitude: 6000,
                meaning: "overcast",
                cumulonimbus: true
            }], m.clouds);
        });

    });

    describe("for temp/dewpoint", function() {
        it("can parse it", function() {
            var m = parseMetar("EFKI 171950Z 00000KT 9999 MIFG FEW012 SCT200 10/11 Q1006");
                assert.equal(10, m.temperature);
                assert.equal(11, m.dewpoint);
        });

        it("can parse neg", function() {
            var m = parseMetar("KLZZ 302355Z AUTO 00000KT 10SM CLR 04/M02 A3029 RMK AO2 T00391018 10070 20031");
            assert.equal(4, m.temperature);
            assert.equal(-2, m.dewpoint);
        });

        it("can parse both neg", function() {
            var m = parseMetar("CYZF 241700Z 32012G18KT 12SM BKN007 OVC042 M02/M03 A2956 RMK SC7SC1 SLP024");
            assert.equal(-2, m.temperature);
            assert.equal(-3, m.dewpoint);
        });

    });

    describe("for altimeter", function() {
        it("can parse inches mercury", function() {
            var m = parseMetar("EFKI 171950Z 00000KT 9999 MIFG FEW012 SCT200 10/11 Q1006");
            assert.equal(1006, m.altimeter_hpa);
        });

        it("can parse hpa", function() {
            var m = parseMetar("KLZZ 302355Z AUTO 00000KT 10SM CLR 04/M02 A3029 RMK AO2 T00391018 10070 20031");
            assert.equal(30.29, m.altimeter_in_hg);
        });

    });

});
<|endoftext|>


<commit_before>gpf.require.define({}, function () {
    "use strict";

    function _addEventsListener (selector, eventName, handler) {
        [].slice.call(document.querySelectorAll(selector)).forEach(function (oElement) {
            oElement.addEventListener(eventName, handler);
        });
    }

    var api = {

        addEventsListener: function (events) {
            Object.keys(events).forEach(function (eventKey) {
                var eventParts = eventKey.split("@"),
                    selector = eventParts[0],
                    eventName = eventParts[1],
                    handler = events[eventKey];
                if (selector) {
                    _addEventsListener(selector, eventName, handler);
                } else {
                    document.addEventListener(eventName, handler);
                }
            });
        },

        clear: function (element) {
            element.innerHTML = "";
        }

    };

    ["div", "span", "li", "ul"].forEach(function (tagName) {
        api[tagName] = gpf.web.createTagFunction(tagName);
    });

    return api;

});
<commit_msg>Adds other tags (#183)<commit_after>gpf.require.define({}, function () {
    "use strict";

    function _addEventsListener (selector, eventName, handler) {
        [].slice.call(document.querySelectorAll(selector)).forEach(function (oElement) {
            oElement.addEventListener(eventName, handler);
        });
    }

    var api = {

        addEventsListener: function (events) {
            Object.keys(events).forEach(function (eventKey) {
                var eventParts = eventKey.split("@"),
                    selector = eventParts[0],
                    eventName = eventParts[1],
                    handler = events[eventKey];
                if (selector) {
                    _addEventsListener(selector, eventName, handler);
                } else {
                    document.addEventListener(eventName, handler);
                }
            });
        },

        clear: function (element) {
            element.innerHTML = "";
        }

    };

    [
        "div",
        "span",
        "li",
        "ul",
        "label",
        "select",
        "optgroup",
        "option"

    ].forEach(function (tagName) {
        api[tagName] = gpf.web.createTagFunction(tagName);
    });

    return api;

});
<|endoftext|>


<commit_before>// replies-api-test.js
//
// Test replies over the API
//
// Copyright 2012, E14N https://e14n.com/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var assert = require("assert"),
    vows = require("vows"),
    Step = require("step"),
    _ = require("underscore"),
    http = require("http"),
    urlparse = require("url").parse,
    httputil = require("./lib/http"),
    oauthutil = require("./lib/oauth"),
    actutil = require("./lib/activity"),
    setupApp = oauthutil.setupApp,
    register = oauthutil.register,
    accessToken = oauthutil.accessToken,
    newCredentials = oauthutil.newCredentials;

var suite = vows.describe("Activity API test");

var assertGoodCred = function(cred) {
    assert.isObject(cred);
    assert.isString(cred.consumer_key);
    assert.isString(cred.consumer_secret);
    assert.isString(cred.token);
    assert.isString(cred.token_secret);
};

// A batch for testing the read-write access to the API

suite.addBatch({
    "When we set up the app": {
        topic: function() {
            setupApp(this.callback);
        },
        teardown: function(app) {
            if (app && app.close) {
                app.close();
            }
        },
        "it works": function(err, app) {
            assert.ifError(err);
        },
        "and we get new credentials": {
            topic: function() {
                newCredentials("macdonald", "the|old|flag", this.callback);
            },
            "it works": function(err, cred) {
                assert.ifError(err);
                assertGoodCred(cred);
            },
            "and we post a new activity": {
                topic: function(cred) {
                    var cb = this.callback,
                        act = {
                            verb: "post",
                            object: {
                                objectType: "note",
                                content: "The people would prefer John A. drunk to George Brown sober."
                            }
                        };
                    httputil.postJSON("http://localhost:4815/api/user/macdonald/feed", cred, act, function(err, act, response) {
                        cb(err, act);
                    });
                },
                "it works": function(err, act) {
                    assert.ifError(err);
                    assert.isObject(act);
                },
                "the object includes a replies property": function(err, act) {
                    assert.ifError(err);
                    assert.isObject(act);
                    assert.includes(act, "object");
                    assert.isObject(act.object);
                    assert.includes(act.object, "replies");
                    assert.isObject(act.object.replies);
                    assert.includes(act.object.replies, "url");
                    assert.isString(act.object.replies.url);
                    assert.includes(act.object.replies, "totalItems");
                    assert.isNumber(act.object.replies.totalItems);
                    assert.equal(act.object.replies.totalItems, 0);
                },
                "and we fetch the replies feed": {
                    topic: function(act, cred) {
                        var cb = this.callback,
                            url = act.object.replies.url;

                        httputil.getJSON(url, cred, function(err, coll, response) {
                            cb(err, coll);
                        });
                    },
                    "it works": function(err, coll) {
                        assert.ifError(err);
                        assert.isObject(coll);
                    },
                    "it is an empty collection": function(err, coll) {
                        assert.ifError(err);
                        assert.isObject(coll);
                        assert.includes(coll, "id");
                        assert.isString(coll.id);
                        assert.includes(coll, "totalItems");
                        assert.isNumber(coll.totalItems);
                        assert.equal(coll.totalItems, 0);
                        assert.includes(coll, "items");
                        assert.isArray(coll.items);
                        assert.lengthOf(coll.items, 0);
                    }
                }
            }
        },
        "and we make two new sets of credentials": {
            topic: function() {
                Step(
                    function() {
                        newCredentials("mackenzie", "rail*road", this.parallel());
                        newCredentials("thompson", "bering*sea", this.parallel());
                    },
                    this.callback
                );
            },
            "it works": function(err, cred1, cred2) {
                assert.ifError(err);
                assertGoodCred(cred1);
                assertGoodCred(cred2);
            },
            "and we post a photo and a comment": {
                topic: function(cred1, cred2) {
                    var cb = this.callback,
                        photo;

                    Step(
                        function() {
                            var act = {
                                verb: "post",
                                object: {
                                    objectType: "image",
                                    url: "http://photos.example/1",
                                    summary: "New Parliament Buildings."
                                }
                            };
                            httputil.postJSON("http://localhost:4815/api/user/mackenzie/feed", cred1, act, this);
                        },
                        function(err, act, response) {
                            var reply;
                            if (err) throw err;
                            photo = act;
                            reply = {
                                verb: "post",
                                object: {
                                    objectType: "comment",
                                    content: "Nice one!",
                                    inReplyTo: act.object
                                }
                            };
                            httputil.postJSON("http://localhost:4815/api/user/thompson/feed", cred2, reply, this);
                        },
                        function(err, reply, response) {
                            cb(err, photo, reply);
                        }
                    );
                },
                "it works": function(err, photo, reply) {
                    assert.ifError(err);
                    assert.isObject(photo);
                    assert.isObject(reply);
                    assert.include(photo, "id");
                    assert.include(photo, "object");
                    assert.include(photo.object, "replies");
                    assert.include(photo.object.replies, "url");
                    assert.isString(photo.object.replies.url);
                    assert.include(reply, "object");
                    assert.include(reply.object, "inReplyTo");
                    assert.include(reply.object.inReplyTo, "id");
                    assert.equal(reply.object.inReplyTo.id, photo.object.id);
                },
                "and we check the replies feed": {
                    topic: function(photo, reply, cred1, cred2) {
                        var cb = this.callback,
                            url = photo.object.replies.url;

                        httputil.getJSON(url, cred1, function(err, coll, response) {
                            cb(err, coll, reply);
                        });
                    },
                    "it works": function(err, coll, reply) {
                        assert.ifError(err);
                        assert.isObject(coll);
                    },
                    "it includes our reply": function(err, coll, reply) {
                        assert.ifError(err);
                        assert.isObject(coll);
                        assert.includes(coll, "totalItems");
                        assert.isNumber(coll.totalItems);
                        assert.equal(coll.totalItems, 1);
                        assert.includes(coll, "items");
                        assert.isArray(coll.items);
                        assert.lengthOf(coll.items, 1);
                        assert.equal(coll.items[0].id, reply.object.id);
                    },
                    "and we delete the reply and re-check the feed": {
                        topic: function(coll, reply, photo, replyAgain, cred1, cred2) {
                            var cb = this.callback;

                            Step(
                                function() {
                                    httputil.delJSON(reply.object.id, cred2, this);
                                },
                                function(err, del, response) {
                                    if (err) throw err;
                                    var url = photo.object.replies.url;
                                    httputil.getJSON(url, cred1, this);
                                },
                                function(err, coll, response) {
                                    cb(err, coll);
                                }
                            );
                        },
                        "it works": function(err, coll) {
                            assert.ifError(err);
                            assert.isObject(coll);
                        },
                        "it is an empty collection": function(err, coll) {
                            assert.ifError(err);
                            assert.isObject(coll);
                            assert.includes(coll, "id");
                            assert.isString(coll.id);
                            assert.includes(coll, "totalItems");
                            assert.isNumber(coll.totalItems);
                            assert.equal(coll.totalItems, 0);
                            assert.includes(coll, "items");
                            assert.isArray(coll.items);
                            assert.lengthOf(coll.items, 0);
                        }
                    }
                }
            }
        },
        "and we make two more new sets of credentials": {
            topic: function() {
                Step(
                    function() {
                        newCredentials("laurier", "moderation!", this.parallel());
                        newCredentials("borden", "over there,", this.parallel());
                    },
                    this.callback
                );
            },
            "it works": function(err, cred1, cred2) {
                assert.ifError(err);
                assertGoodCred(cred1);
                assertGoodCred(cred2);
            },
            "and we post a note and a lot of comments": {

                topic: function(cred1, cred2) {
                    var cb = this.callback,
                        note;

                    Step(
                        function() {
                            var act = {
                                verb: "post",
                                object: {
                                    objectType: "note",
                                    summary: "I must get back to work."
                                }
                            };
                            httputil.postJSON("http://localhost:4815/api/user/laurier/feed", cred1, act, this);
                        },
                        function(err, act, response) {
                            var i, comment, group = this.group();
                            if (err) throw err;
                            note = act;
                            for (i = 0; i < 100; i++) {
                                comment = {
                                    verb: "post",
                                    object: {
                                        objectType: "comment",
                                        content: "FIRST POST",
                                        inReplyTo: act.object
                                    }
                                };
                                httputil.postJSON("http://localhost:4815/api/user/borden/feed",
                                                  cred2,
                                                  comment,
                                                  group());
                            }
                        },
                        function(err, comments, responses) {
                            cb(err, note, comments);
                        }
                    );
                },
                "it works": function(err, note, comments) {
                    assert.ifError(err);
                    assert.isObject(note);
                    assert.isArray(comments);
                    assert.lengthOf(comments, 100);
                },
                "and we get the full replies feed": {
                    topic: function(note, comments, cred1, cred2) {
                        var cb = this.callback,
                            url = note.object.replies.url + "?count=100";

                        httputil.getJSON(url, cred1, function(err, coll, response) {
                            cb(err, coll, comments);
                        });
                    },
                    "it works": function(err, coll, comments) {
                        assert.ifError(err);
                        assert.isObject(coll);
                        assert.isArray(comments);
                    },
                    "it has the right data": function(err, coll, comments) {
                        var i,
                            collIDs = {},
                            commentIDs = {};
                        assert.isObject(coll);
                        assert.includes(coll, "id");
                        assert.isString(coll.id);
                        assert.includes(coll, "totalItems");
                        assert.isNumber(coll.totalItems);
                        assert.equal(coll.totalItems, 100);
                        assert.includes(coll, "items");
                        assert.isArray(coll.items);
                        assert.lengthOf(coll.items, 100);

                        for (i = 0; i < 100; i++) {
                            collIDs[coll.items[i].id] = 1;
                            commentIDs[comments[i].object.id] = 1;
                        }

                        for (i = 0; i < 100; i++) {
                            assert.include(collIDs, comments[i].object.id);
                            assert.include(commentIDs, coll.items[i].id);
                        }
                    }
                },
                "and we get the original item": {
                    topic: function(note, comments, cred1, cred2) {
                        var cb = this.callback,
                            url = note.object.id;

                        httputil.getJSON(url, cred1, function(err, note, response) {
                            cb(err, note, comments);
                        });
                    },
                    "it works": function(err, note, comments) {
                        assert.ifError(err);
                        assert.isObject(note);
                        assert.isArray(comments);
                    },
                    "it has the correct replies": function(err, note, comments) {
                        var i, commentIDs = {};

                        assert.ifError(err);
                        assert.isObject(note);
                        assert.include(note, "replies");
                        assert.include(note.replies, "totalItems");
                        assert.isNumber(note.replies.totalItems);
                        assert.equal(note.replies.totalItems, 100);
                        assert.include(note.replies, "items");
                        assert.isArray(note.replies.items);

                        for (i = 0; i < 100; i++) {
                            commentIDs[comments[i].object.id] = 1;
                        }

                        for (i = 0; i < note.replies.items.length; i++) {
                            assert.include(commentIDs, note.replies.items[i].id);
                        }
                    }
                }
            }
        }
    }
});

suite["export"](module);
<commit_msg>Remove accessToken import from replies-api-test<commit_after>// replies-api-test.js
//
// Test replies over the API
//
// Copyright 2012, E14N https://e14n.com/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var assert = require("assert"),
    vows = require("vows"),
    Step = require("step"),
    _ = require("underscore"),
    http = require("http"),
    urlparse = require("url").parse,
    httputil = require("./lib/http"),
    oauthutil = require("./lib/oauth"),
    actutil = require("./lib/activity"),
    setupApp = oauthutil.setupApp,
    register = oauthutil.register,
    newCredentials = oauthutil.newCredentials;

var suite = vows.describe("Activity API test");

var assertGoodCred = function(cred) {
    assert.isObject(cred);
    assert.isString(cred.consumer_key);
    assert.isString(cred.consumer_secret);
    assert.isString(cred.token);
    assert.isString(cred.token_secret);
};

// A batch for testing the read-write access to the API

suite.addBatch({
    "When we set up the app": {
        topic: function() {
            setupApp(this.callback);
        },
        teardown: function(app) {
            if (app && app.close) {
                app.close();
            }
        },
        "it works": function(err, app) {
            assert.ifError(err);
        },
        "and we get new credentials": {
            topic: function() {
                newCredentials("macdonald", "the|old|flag", this.callback);
            },
            "it works": function(err, cred) {
                assert.ifError(err);
                assertGoodCred(cred);
            },
            "and we post a new activity": {
                topic: function(cred) {
                    var cb = this.callback,
                        act = {
                            verb: "post",
                            object: {
                                objectType: "note",
                                content: "The people would prefer John A. drunk to George Brown sober."
                            }
                        };
                    httputil.postJSON("http://localhost:4815/api/user/macdonald/feed", cred, act, function(err, act, response) {
                        cb(err, act);
                    });
                },
                "it works": function(err, act) {
                    assert.ifError(err);
                    assert.isObject(act);
                },
                "the object includes a replies property": function(err, act) {
                    assert.ifError(err);
                    assert.isObject(act);
                    assert.includes(act, "object");
                    assert.isObject(act.object);
                    assert.includes(act.object, "replies");
                    assert.isObject(act.object.replies);
                    assert.includes(act.object.replies, "url");
                    assert.isString(act.object.replies.url);
                    assert.includes(act.object.replies, "totalItems");
                    assert.isNumber(act.object.replies.totalItems);
                    assert.equal(act.object.replies.totalItems, 0);
                },
                "and we fetch the replies feed": {
                    topic: function(act, cred) {
                        var cb = this.callback,
                            url = act.object.replies.url;

                        httputil.getJSON(url, cred, function(err, coll, response) {
                            cb(err, coll);
                        });
                    },
                    "it works": function(err, coll) {
                        assert.ifError(err);
                        assert.isObject(coll);
                    },
                    "it is an empty collection": function(err, coll) {
                        assert.ifError(err);
                        assert.isObject(coll);
                        assert.includes(coll, "id");
                        assert.isString(coll.id);
                        assert.includes(coll, "totalItems");
                        assert.isNumber(coll.totalItems);
                        assert.equal(coll.totalItems, 0);
                        assert.includes(coll, "items");
                        assert.isArray(coll.items);
                        assert.lengthOf(coll.items, 0);
                    }
                }
            }
        },
        "and we make two new sets of credentials": {
            topic: function() {
                Step(
                    function() {
                        newCredentials("mackenzie", "rail*road", this.parallel());
                        newCredentials("thompson", "bering*sea", this.parallel());
                    },
                    this.callback
                );
            },
            "it works": function(err, cred1, cred2) {
                assert.ifError(err);
                assertGoodCred(cred1);
                assertGoodCred(cred2);
            },
            "and we post a photo and a comment": {
                topic: function(cred1, cred2) {
                    var cb = this.callback,
                        photo;

                    Step(
                        function() {
                            var act = {
                                verb: "post",
                                object: {
                                    objectType: "image",
                                    url: "http://photos.example/1",
                                    summary: "New Parliament Buildings."
                                }
                            };
                            httputil.postJSON("http://localhost:4815/api/user/mackenzie/feed", cred1, act, this);
                        },
                        function(err, act, response) {
                            var reply;
                            if (err) throw err;
                            photo = act;
                            reply = {
                                verb: "post",
                                object: {
                                    objectType: "comment",
                                    content: "Nice one!",
                                    inReplyTo: act.object
                                }
                            };
                            httputil.postJSON("http://localhost:4815/api/user/thompson/feed", cred2, reply, this);
                        },
                        function(err, reply, response) {
                            cb(err, photo, reply);
                        }
                    );
                },
                "it works": function(err, photo, reply) {
                    assert.ifError(err);
                    assert.isObject(photo);
                    assert.isObject(reply);
                    assert.include(photo, "id");
                    assert.include(photo, "object");
                    assert.include(photo.object, "replies");
                    assert.include(photo.object.replies, "url");
                    assert.isString(photo.object.replies.url);
                    assert.include(reply, "object");
                    assert.include(reply.object, "inReplyTo");
                    assert.include(reply.object.inReplyTo, "id");
                    assert.equal(reply.object.inReplyTo.id, photo.object.id);
                },
                "and we check the replies feed": {
                    topic: function(photo, reply, cred1, cred2) {
                        var cb = this.callback,
                            url = photo.object.replies.url;

                        httputil.getJSON(url, cred1, function(err, coll, response) {
                            cb(err, coll, reply);
                        });
                    },
                    "it works": function(err, coll, reply) {
                        assert.ifError(err);
                        assert.isObject(coll);
                    },
                    "it includes our reply": function(err, coll, reply) {
                        assert.ifError(err);
                        assert.isObject(coll);
                        assert.includes(coll, "totalItems");
                        assert.isNumber(coll.totalItems);
                        assert.equal(coll.totalItems, 1);
                        assert.includes(coll, "items");
                        assert.isArray(coll.items);
                        assert.lengthOf(coll.items, 1);
                        assert.equal(coll.items[0].id, reply.object.id);
                    },
                    "and we delete the reply and re-check the feed": {
                        topic: function(coll, reply, photo, replyAgain, cred1, cred2) {
                            var cb = this.callback;

                            Step(
                                function() {
                                    httputil.delJSON(reply.object.id, cred2, this);
                                },
                                function(err, del, response) {
                                    if (err) throw err;
                                    var url = photo.object.replies.url;
                                    httputil.getJSON(url, cred1, this);
                                },
                                function(err, coll, response) {
                                    cb(err, coll);
                                }
                            );
                        },
                        "it works": function(err, coll) {
                            assert.ifError(err);
                            assert.isObject(coll);
                        },
                        "it is an empty collection": function(err, coll) {
                            assert.ifError(err);
                            assert.isObject(coll);
                            assert.includes(coll, "id");
                            assert.isString(coll.id);
                            assert.includes(coll, "totalItems");
                            assert.isNumber(coll.totalItems);
                            assert.equal(coll.totalItems, 0);
                            assert.includes(coll, "items");
                            assert.isArray(coll.items);
                            assert.lengthOf(coll.items, 0);
                        }
                    }
                }
            }
        },
        "and we make two more new sets of credentials": {
            topic: function() {
                Step(
                    function() {
                        newCredentials("laurier", "moderation!", this.parallel());
                        newCredentials("borden", "over there,", this.parallel());
                    },
                    this.callback
                );
            },
            "it works": function(err, cred1, cred2) {
                assert.ifError(err);
                assertGoodCred(cred1);
                assertGoodCred(cred2);
            },
            "and we post a note and a lot of comments": {

                topic: function(cred1, cred2) {
                    var cb = this.callback,
                        note;

                    Step(
                        function() {
                            var act = {
                                verb: "post",
                                object: {
                                    objectType: "note",
                                    summary: "I must get back to work."
                                }
                            };
                            httputil.postJSON("http://localhost:4815/api/user/laurier/feed", cred1, act, this);
                        },
                        function(err, act, response) {
                            var i, comment, group = this.group();
                            if (err) throw err;
                            note = act;
                            for (i = 0; i < 100; i++) {
                                comment = {
                                    verb: "post",
                                    object: {
                                        objectType: "comment",
                                        content: "FIRST POST",
                                        inReplyTo: act.object
                                    }
                                };
                                httputil.postJSON("http://localhost:4815/api/user/borden/feed",
                                                  cred2,
                                                  comment,
                                                  group());
                            }
                        },
                        function(err, comments, responses) {
                            cb(err, note, comments);
                        }
                    );
                },
                "it works": function(err, note, comments) {
                    assert.ifError(err);
                    assert.isObject(note);
                    assert.isArray(comments);
                    assert.lengthOf(comments, 100);
                },
                "and we get the full replies feed": {
                    topic: function(note, comments, cred1, cred2) {
                        var cb = this.callback,
                            url = note.object.replies.url + "?count=100";

                        httputil.getJSON(url, cred1, function(err, coll, response) {
                            cb(err, coll, comments);
                        });
                    },
                    "it works": function(err, coll, comments) {
                        assert.ifError(err);
                        assert.isObject(coll);
                        assert.isArray(comments);
                    },
                    "it has the right data": function(err, coll, comments) {
                        var i,
                            collIDs = {},
                            commentIDs = {};
                        assert.isObject(coll);
                        assert.includes(coll, "id");
                        assert.isString(coll.id);
                        assert.includes(coll, "totalItems");
                        assert.isNumber(coll.totalItems);
                        assert.equal(coll.totalItems, 100);
                        assert.includes(coll, "items");
                        assert.isArray(coll.items);
                        assert.lengthOf(coll.items, 100);

                        for (i = 0; i < 100; i++) {
                            collIDs[coll.items[i].id] = 1;
                            commentIDs[comments[i].object.id] = 1;
                        }

                        for (i = 0; i < 100; i++) {
                            assert.include(collIDs, comments[i].object.id);
                            assert.include(commentIDs, coll.items[i].id);
                        }
                    }
                },
                "and we get the original item": {
                    topic: function(note, comments, cred1, cred2) {
                        var cb = this.callback,
                            url = note.object.id;

                        httputil.getJSON(url, cred1, function(err, note, response) {
                            cb(err, note, comments);
                        });
                    },
                    "it works": function(err, note, comments) {
                        assert.ifError(err);
                        assert.isObject(note);
                        assert.isArray(comments);
                    },
                    "it has the correct replies": function(err, note, comments) {
                        var i, commentIDs = {};

                        assert.ifError(err);
                        assert.isObject(note);
                        assert.include(note, "replies");
                        assert.include(note.replies, "totalItems");
                        assert.isNumber(note.replies.totalItems);
                        assert.equal(note.replies.totalItems, 100);
                        assert.include(note.replies, "items");
                        assert.isArray(note.replies.items);

                        for (i = 0; i < 100; i++) {
                            commentIDs[comments[i].object.id] = 1;
                        }

                        for (i = 0; i < note.replies.items.length; i++) {
                            assert.include(commentIDs, note.replies.items[i].id);
                        }
                    }
                }
            }
        }
    }
});

suite["export"](module);
<|endoftext|>


<commit_before>/* eslint-env jasmine */
const nock = require('nock')
const VoucherifyClient = require('./client-loader')
const fixtures = require('./fixtures')
const reqWithoutBody = fixtures.reqWithoutBody
const reqWithBody = fixtures.reqWithBody
nock.disableNetConnect()

describe('Rewards API', function () {
  const client = new VoucherifyClient({
    applicationId: 'node-sdk-test-id',
    clientSecretKey: 'node-sdk-test-secret'
  })

  it('should create reward', function (done) {
    const server = nock('https://api.voucherify.io', reqWithBody)
    .post('/v1/rewards', {
      name: '5$ discount',
      parameters: {
        campaign: {
          id: 'camp_gtNM5oQJybruzANwYv1mgHk6'
        }
      }
    })
    .reply(200, {})

    client.rewards.create({
      name: '5$ discount',
      parameters: {
        campaign: {
          id: 'camp_gtNM5oQJybruzANwYv1mgHk6'
        }
      }
    })
    .then(function () {
      server.done()
      done()
    })
  })

  it('should get reward', function (done) {
    const server = nock('https://api.voucherify.io', reqWithoutBody)
    .get('/v1/rewards/reward_test-id')
    .reply(200, {})

    client.rewards.get('reward_test-id')
    .then(function () {
      server.done()
      done()
    })
  })

  it('should update reward', function (done) {
    const server = nock('https://api.voucherify.io', reqWithBody)
    .put('/v1/rewards/reward_test-id', {
      name: '10$ discount'
    })
    .reply(200, {})

    client.rewards.update({
      id: 'reward_test-id',
      name: '10$ discount'
    })
    .then(function () {
      server.done()
      done()
    })
  })

  it('should delete reward', function (done) {
    const server = nock('https://api.voucherify.io', reqWithoutBody)
    .delete('/v1/rewards/reward_test-id')
    .reply(200, {})

    client.rewards.delete('reward_test-id')
    .then(function () {
      server.done()
      done()
    })
  })

  describe('list', function () {
    it('should list all', function (done) {
      const server = nock('https://api.voucherify.io', reqWithoutBody)
        .get('/v1/rewards')
        .reply(200, {})

      client.rewards.list()
        .then(function () {
          server.done()
          done()
        })
    })

    it('should list all (callback)', function (done) {
      const server = nock('https://api.voucherify.io', reqWithoutBody)
        .get('/v1/rewards')
        .reply(200, [])

      client.rewards.list(function (err) {
        expect(err).toBeNull()
        server.done()
        done()
      })
    })

    it('should list by query', function (done) {
      const server = nock('https://api.voucherify.io', reqWithoutBody)
        .get('/v1/rewards')
        .query({limit: 100})
        .reply(200, {})

      client.rewards.list({limit: 100})
        .then(function () {
          server.done()
          done()
        })
    })
  })

  describe('reward assignments', function () {
    it('should create reward assignment', function (done) {
      const server = nock('https://api.voucherify.io', reqWithBody)
        .post('/v1/rewards/reward_test-id/assignments', {
          campaign: 'camp_yGEDLqgb9Es1ldwqU3K9PYv0',
          parameters: {
            loyalty: {
              points: 200
            }
          }
        })
        .reply(200, {})

      client.rewards.createAssignment('reward_test-id', {
        campaign: 'camp_yGEDLqgb9Es1ldwqU3K9PYv0',
        parameters: {
          loyalty: {
            points: 200
          }
        }
      })
      .then(function () {
        server.done()
        done()
      })
    })

    it('should update reward assignment', function (done) {
      const server = nock('https://api.voucherify.io', reqWithBody)
        .put('/v1/rewards/reward_test-id/assignments/reward_assignment_test-id', {
          parameters: {
            loyalty: {
              points: 100
            }
          }
        })
        .reply(200, {})

      client.rewards.updateAssignment('reward_test-id', {
        id: 'reward_assignment_test-id',
        parameters: {
          loyalty: {
            points: 100
          }
        }
      })
      .then(function () {
        server.done()
        done()
      })
    })

    it('should delete reward assignment', function (done) {
      const server = nock('https://api.voucherify.io', reqWithoutBody)
        .delete('/v1/rewards/reward_test-id/assignments/reward_assignment_test-id')
        .reply(200, {})

      client.rewards.deleteAssignment('reward_test-id', 'reward_assignment_test-id')
      .then(function () {
        server.done()
        done()
      })
    })

    describe('list', function () {
      it('should list all', function (done) {
        const server = nock('https://api.voucherify.io', reqWithoutBody)
          .get('/v1/rewards/reward_test-id/assignments')
          .reply(200, {})

        client.rewards.listAssignments('reward_test-id')
          .then(function () {
            server.done()
            done()
          })
      })

      it('should list all (callback)', function (done) {
        const server = nock('https://api.voucherify.io', reqWithoutBody)
          .get('/v1/rewards/reward_test-id/assignments')
          .reply(200, [])

        client.rewards.listAssignments('reward_test-id', function (err) {
          expect(err).toBeNull()
          server.done()
          done()
        })
      })

      it('should list by query', function (done) {
        const server = nock('https://api.voucherify.io', reqWithoutBody)
          .get('/v1/rewards/reward_test-id/assignments')
          .query({limit: 100})
          .reply(200, {})

        client.rewards.listAssignments('reward_test-id', {limit: 100})
          .then(function () {
            server.done()
            done()
          })
      })
    })
  })
})
<commit_msg>Improve test fro rewards<commit_after>/* eslint-env jasmine */
const nock = require('nock')
const VoucherifyClient = require('./client-loader')
const fixtures = require('./fixtures')
const reqWithoutBody = fixtures.reqWithoutBody
const reqWithBody = fixtures.reqWithBody
nock.disableNetConnect()

describe('Rewards API', function () {
  const client = new VoucherifyClient({
    applicationId: 'node-sdk-test-id',
    clientSecretKey: 'node-sdk-test-secret'
  })

  it('should create reward', function (done) {
    const server = nock('https://api.voucherify.io', reqWithBody)
      .post('/v1/rewards', {
        name: '5$ discount',
        parameters: {
          campaign: {
            id: 'camp_gtNM5oQJybruzANwYv1mgHk6'
          }
        }
      })
      .reply(200, {})

    client.rewards.create({
      name: '5$ discount',
      parameters: {
        campaign: {
          id: 'camp_gtNM5oQJybruzANwYv1mgHk6'
        }
      }
    })
      .then(function () {
        server.done()
        done()
      })
  })

  it('should create reward (callback)', function (done) {
    const server = nock('https://api.voucherify.io', reqWithBody)
      .post('/v1/rewards', {
        name: '5$ discount',
        parameters: {
          campaign: {
            id: 'camp_gtNM5oQJybruzANwYv1mgHk6'
          }
        }
      })
      .reply(200, {})

    client.rewards.create({
      name: '5$ discount',
      parameters: {
        campaign: {
          id: 'camp_gtNM5oQJybruzANwYv1mgHk6'
        }
      }
    }, function (err) {
      expect(err).toBeNull()
      server.done()
      done()
    })
  })

  it('should get reward', function (done) {
    const server = nock('https://api.voucherify.io', reqWithoutBody)
      .get('/v1/rewards/reward_test-id')
      .reply(200, {})

    client.rewards.get('reward_test-id')
      .then(function () {
        server.done()
        done()
      })
  })

  // Unexpected timeout ?
  // it('should get reward (callback)', function (done) {
  //   const server = nock('https://api.voucherify.io', reqWithoutBody)
  //     .get('/v1/rewards/reward_test-id')
  //     .reply(200, {})
  //
  //   client.rewards.get('reward_test-id', function (err) {
  //     expect(err).toBeNull()
  //     server.done()
  //     done()
  //   })
  // })

  it('should update reward', function (done) {
    const server = nock('https://api.voucherify.io', reqWithBody)
      .put('/v1/rewards/reward_test-id', {
        name: '10$ discount'
      })
      .reply(200, {})

    client.rewards.update({
      id: 'reward_test-id',
      name: '10$ discount'
    })
      .then(function () {
        server.done()
        done()
      })
  })

  it('should update reward (callback)', function (done) {
    const server = nock('https://api.voucherify.io', reqWithBody)
      .put('/v1/rewards/reward_test-id', {
        name: '10$ discount'
      })
      .reply(200, {})

    client.rewards.update({
      id: 'reward_test-id',
      name: '10$ discount'
    }, function (err) {
      expect(err).toBeNull()
      server.done()
      done()
    })
  })

  it('should delete reward', function (done) {
    const server = nock('https://api.voucherify.io', reqWithoutBody)
      .delete('/v1/rewards/reward_test-id')
      .reply(200, {})

    client.rewards.delete('reward_test-id')
      .then(function () {
        server.done()
        done()
      })
  })

  it('should delete reward (callback)', function (done) {
    const server = nock('https://api.voucherify.io', reqWithoutBody)
      .delete('/v1/rewards/reward_test-id')
      .reply(200, {})

    client.rewards.delete('reward_test-id', function (err) {
      expect(err).toBeNull()
      server.done()
      done()
    })
  })

  it('should list all rewards', function (done) {
    const server = nock('https://api.voucherify.io', reqWithoutBody)
      .get('/v1/rewards')
      .reply(200, {})

    client.rewards.list()
      .then(function () {
        server.done()
        done()
      })
  })

  it('should list all rewards (callback)', function (done) {
    const server = nock('https://api.voucherify.io', reqWithoutBody)
      .get('/v1/rewards')
      .reply(200, [])

    client.rewards.list(function (err) {
      expect(err).toBeNull()
      server.done()
      done()
    })
  })

  it('should list all rewards by query', function (done) {
    const server = nock('https://api.voucherify.io', reqWithoutBody)
      .get('/v1/rewards')
      .query({limit: 100})
      .reply(200, {})

    client.rewards.list({limit: 100})
      .then(function () {
        server.done()
        done()
      })
  })

  it('should list all rewards by query (callback)', function (done) {
    const server = nock('https://api.voucherify.io', reqWithoutBody)
      .get('/v1/rewards')
      .query({limit: 100})
      .reply(200, {})

    client.rewards.list({limit: 100}, function (err) {
      expect(err).toBeNull()
      server.done()
      done()
    })
  })

  describe('reward assignment', function () {
    it('should create reward assignment', function (done) {
      const server = nock('https://api.voucherify.io', reqWithBody)
        .post('/v1/rewards/reward_test-id/assignments', {
          campaign: 'camp_yGEDLqgb9Es1ldwqU3K9PYv0',
          parameters: {
            loyalty: {
              points: 200
            }
          }
        })
        .reply(200, {})

      client.rewards.createAssignment('reward_test-id', {
        campaign: 'camp_yGEDLqgb9Es1ldwqU3K9PYv0',
        parameters: {
          loyalty: {
            points: 200
          }
        }
      })
        .then(function () {
          server.done()
          done()
        })
    })

    it('should update reward assignment', function (done) {
      const server = nock('https://api.voucherify.io', reqWithBody)
        .put('/v1/rewards/reward_test-id/assignments/reward_assignment_test-id', {
          parameters: {
            loyalty: {
              points: 100
            }
          }
        })
        .reply(200, {})

      client.rewards.updateAssignment('reward_test-id', {
        id: 'reward_assignment_test-id',
        parameters: {
          loyalty: {
            points: 100
          }
        }
      })
        .then(function () {
          server.done()
          done()
        })
    })

    it('should delete reward assignment', function (done) {
      const server = nock('https://api.voucherify.io', reqWithoutBody)
        .delete('/v1/rewards/reward_test-id/assignments/reward_assignment_test-id')
        .reply(200, {})

      client.rewards.deleteAssignment('reward_test-id', 'reward_assignment_test-id')
        .then(function () {
          server.done()
          done()
        })
    })

    it('should list all reward assignments', function (done) {
      const server = nock('https://api.voucherify.io', reqWithoutBody)
        .get('/v1/rewards/reward_test-id/assignments')
        .reply(200, {})

      client.rewards.listAssignments('reward_test-id')
        .then(function () {
          server.done()
          done()
        })
    })

    it('should list reward assignments by query', function (done) {
      const server = nock('https://api.voucherify.io', reqWithoutBody)
        .get('/v1/rewards/reward_test-id/assignments')
        .query({limit: 100})
        .reply(200, {})

      client.rewards.listAssignments('reward_test-id', {limit: 100})
        .then(function () {
          server.done()
          done()
        })
    })

    it('should list all reward assignments (callback)', function (done) {
      const server = nock('https://api.voucherify.io', reqWithoutBody)
        .get('/v1/rewards/reward_test-id/assignments')
        .reply(200, [])

      client.rewards.listAssignments('reward_test-id', function (err) {
        expect(err).toBeNull()
        server.done()
        done()
      })
    })
  })
})
<|endoftext|>


<commit_before>var http = require('http');
http.createServer(function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World: ' + req.method + '\n');
}).listen(80, '0.0.0.0');
console.log('Server running at http://127.0.0.1:1337/');
<commit_msg>Return a JSON object instead of plain text.<commit_after>var http = require('http');
http.createServer(function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.end(JSON.stringify({
        message: 'Answered ' + req.method + ' request from ' + req.headers.host
    }));
}).listen(80, '0.0.0.0');
console.log('Server running at http://127.0.0.1:1337/');
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var async = require('async');
var monitor = require('./monitor');
var detect = require('./detect');
var CLOSED_STATES = [ 'closed', 'failed' ];

/**
  ## rtc/couple

  ### couple(pc, targetId, signaller, opts?)

  Couple a WebRTC connection with another webrtc connection identified by
  `targetId` via the signaller.

  The following options can be provided in the `opts` argument:

  - `sdpfilter` (default: null)

    A simple function for filtering SDP as part of the peer
    connection handshake (see the Using Filters details below).

  #### Example Usage

  ```js
  var couple = require('rtc/couple');

  couple(pc, '54879965-ce43-426e-a8ef-09ac1e39a16d', signaller);
  ```

  #### Using Filters

  In certain instances you may wish to modify the raw SDP that is provided
  by the `createOffer` and `createAnswer` calls.  This can be done by passing
  a `sdpfilter` function (or array) in the options.  For example:

  ```js
  // run the sdp from through a local tweakSdp function.
  couple(pc, '54879965-ce43-426e-a8ef-09ac1e39a16d', signaller, {
    sdpfilter: tweakSdp
  });
  ```

**/
function couple(pc, targetId, signaller, opts) {
  var debug = require('cog/logger')('couple');

  // create a monitor for the connection
  var mon = monitor(pc);
  var queuedCandidates = [];
  var sdpFilter = (opts || {}).sdpfilter;
  var reactive = (opts || {}).reactive;
  var offerTimeout;

  // configure the time to wait between receiving a 'disconnect'
  // iceConnectionState and determining that we are closed
  var disconnectTimeout = (opts || {}).disconnectTimeout || 10000;
  var disconnectTimer;

  // if the signaller does not support this isMaster function throw an
  // exception
  if (typeof signaller.isMaster != 'function') {
    throw new Error('rtc-signaller instance >= 0.14.0 required');
  }

  // initilaise the negotiation helpers
  var isMaster = signaller.isMaster(targetId);

  var createOffer = prepNegotiate(
    'createOffer',
    isMaster,
    [ checkStable, checkNotConnecting ]
  );

  var createAnswer = prepNegotiate(
    'createAnswer',
    true,
    [ checkNotConnecting ]
  );

  // initialise the processing queue (one at a time please)
  var q = async.queue(function(task, cb) {
    // if the task has no operation, then trigger the callback immediately
    if (typeof task.op != 'function') {
      return cb();
    }

    // process the task operation
    task.op(task, cb);
  }, 1);

  // initialise session description and icecandidate objects
  var RTCSessionDescription = (opts || {}).RTCSessionDescription ||
    detect('RTCSessionDescription');

  var RTCIceCandidate = (opts || {}).RTCIceCandidate ||
    detect('RTCIceCandidate');

  function abort(stage, sdp, cb) {
    return function(err) {
      // log the error
      console.error('rtc/couple error (' + stage + '): ', err);

      if (typeof cb == 'function') {
        cb(err);
      }
    };
  }

  function applyCandidatesWhenStable() {
    if (pc.signalingState == 'stable' && pc.remoteDescription) {
      debug('signaling state = stable, applying queued candidates');
      mon.removeListener('change', applyCandidatesWhenStable);

      // apply any queued candidates
      queuedCandidates.splice(0).forEach(function(data) {
        debug('applying queued candidate', data);

        try {
          pc.addIceCandidate(new RTCIceCandidate(data));
        }
        catch (e) {
          debug('invalidate candidate specified: ', data);
        }
      });
    }
  }

  function checkNotConnecting(negotiate) {
    if (pc.iceConnectionState != 'checking') {
      return true;
    }

    debug('connection state is checking, will wait to create a new offer');
    mon.once('connected', function() {
      q.push({ op: negotiate });
    });

    return false;
  }

  function checkStable(negotiate) {
    if (pc.signalingState === 'stable') {
      return true;
    }

    debug('cannot create offer, signaling state != stable, will retry');
    mon.on('change', function waitForStable() {
      if (pc.signalingState === 'stable') {
        q.push({ op: negotiate });
      }

      mon.removeListener('change', waitForStable);
    });

    return false;
  }

  function decouple() {
    debug('decoupling ' + signaller.id + ' from ' + targetId);

    // remove local pc event handlers
    pc.onicecandidate = null;
    pc.onnegotiationneeded = null;

    // remove listeners
    signaller.removeListener('sdp', handleSdp);
    signaller.removeListener('candidate', handleRemoteCandidate);
  }

  function prepNegotiate(methodName, allowed, preflightChecks) {
    // ensure we have a valid preflightChecks array
    preflightChecks = [].concat(preflightChecks || []);

    return function negotiate(task, cb) {
      var checksOK = true;

      // if the task is not allowed, then send a negotiate request to our
      // peer
      if (! allowed) {
        signaller.to(targetId).send('/negotiate');
        return cb();
      }

      // run the preflight checks
      preflightChecks.forEach(function(check) {
        checksOK = checksOK && check(negotiate);
      });

      // if the checks have not passed, then abort for the moment
      if (! checksOK) {
        debug('preflight checks did not pass, aborting ' + methodName);
        return cb();
      }

      // create the offer
      debug('calling ' + methodName);
      // debug('gathering state = ' + conn.iceGatheringState);
      // debug('connection state = ' + conn.iceConnectionState);
      // debug('signaling state = ' + conn.signalingState);

      pc[methodName](
        function(desc) {

          // if a filter has been specified, then apply the filter
          if (typeof sdpFilter == 'function') {
            desc.sdp = sdpFilter(desc.sdp, pc, methodName);
          }

          q.push({ op: queueLocalDesc(desc) });
          cb();
        },

        // on error, abort
        abort(methodName, '', cb)
      );
    };
  }

  function handleConnectionClose() {
    debug('captured pc close, iceConnectionState = ' + pc.iceConnectionState);
    decouple();
  }

  function handleDisconnect() {
    debug('captured pc disconnect, monitoring connection status');

    // start the disconnect timer
    disconnectTimer = setTimeout(function() {
      pc.close();
    }, disconnectTimeout);

    mon.on('change', handleDisconnectAbort);
  }

  function handleDisconnectAbort() {
    mon.removeListener('change', handleDisconnectAbort);

    // clear the disconnect timer
    debug('reset disconnect timer, state: ' + pc.iceConnectionState);
    clearTimeout(disconnectTimer);

    // if we have a closed or failed status, then close the connection
    if (CLOSED_STATES.indexOf(pc.iceConnectionState) >= 0) {
      handleConnectionClose();
    }
    // otherwise, reconnect the disconnect monitor
    else {
      mon.once('disconnect', handleDisconnect);
    }
  };

  function handleLocalCandidate(evt) {
    if (evt.candidate) {
      signaller.to(targetId).send('/candidate', evt.candidate);
    }
    else {
      debug('ice gathering state complete');
      signaller.to(targetId).send('/endofcandidates', {});
    }
  }

  function handleRemoteCandidate(data, src) {
    if ((! src) || (src.id !== targetId)) {
      return;
    }

    // queue candidates while the signaling state is not stable
    if (pc.signalingState != 'stable' || (! pc.remoteDescription)) {
      queuedCandidates.push(data);

      mon.removeListener('change', applyCandidatesWhenStable);
      mon.on('change', applyCandidatesWhenStable);
      return;
    }

    try {
      pc.addIceCandidate(new RTCIceCandidate(data));
    }
    catch (e) {
      debug('invalidate candidate specified: ', data);
    }
  }

  function handleSdp(data, src) {
    var abortType = data.type === 'offer' ? 'createAnswer' : 'createOffer';

    // if the source is unknown or not a match, then abort
    if ((! src) || (src.id !== targetId)) {
      return;
    }

    // prioritize setting the remote description operation
    q.push({ op: function(task, cb) {
      // update the remote description
      // once successful, send the answer
      pc.setRemoteDescription(
        new RTCSessionDescription(data),

        function() {
          // create the answer
          if (data.type === 'offer') {
            queue(createAnswer)();
          }

          // trigger the callback
          cb();
        },

        abort(abortType, data.sdp, cb)
      );
    }});
  }

  function queue(negotiateTask) {
    return function() {
      q.push([
        { op: negotiateTask }
      ]);
    };
  }

  function queueLocalDesc(desc) {
    return function setLocalDesc(task, cb) {
      debug('setting local description');

      // initialise the local description
      pc.setLocalDescription(
        desc,

        // if successful, then send the sdp over the wire
        function() {
          // send the sdp
          signaller.to(targetId).send('/sdp', desc);

          // callback
          cb();
        },

        // abort('setLocalDesc', desc.sdp, cb)
        // on error, abort
        function(err) {
          debug('error setting local description', err);
          debug(desc.sdp);
          // setTimeout(function() {
          //   setLocalDesc(task, cb, (retryCount || 0) + 1);
          // }, 500);

          cb(err);
        }
      );
    };
  }

  // if the target id is not a string, then complain
  if (typeof targetId != 'string' && (! (targetId instanceof String))) {
    throw new Error('2nd argument (targetId) should be a string');
  }

  // when regotiation is needed look for the peer
  if (reactive) {
    pc.onnegotiationneeded = function() {
      debug('renegotiation required, will create offer in 50ms');
      clearTimeout(offerTimeout);
      offerTimeout = setTimeout(queue(createOffer), 50);
    };
  }

  pc.onicecandidate = handleLocalCandidate;

  // when we receive sdp, then
  signaller.on('sdp', handleSdp);
  signaller.on('candidate', handleRemoteCandidate);

  // if this is a master connection, listen for negotiate events
  if (isMaster) {
    signaller.on('negotiate', function(src) {
      if (src.id === targetId) {
        debug('got negotiate request from ' + targetId + ', creating offer');
        q.push({ op: createOffer });
      }
    });
  }

  // when the connection closes, remove event handlers
  mon.once('closed', handleConnectionClose);
  mon.once('disconnected', handleDisconnect);

  // patch in the create offer functions
  mon.createOffer = queue(createOffer);

  return mon;
}

module.exports = couple;<commit_msg>Only send /endofcandidates if we actually receieved candidates<commit_after>/* jshint node: true */
'use strict';

var async = require('async');
var monitor = require('./monitor');
var detect = require('./detect');
var CLOSED_STATES = [ 'closed', 'failed' ];

/**
  ## rtc/couple

  ### couple(pc, targetId, signaller, opts?)

  Couple a WebRTC connection with another webrtc connection identified by
  `targetId` via the signaller.

  The following options can be provided in the `opts` argument:

  - `sdpfilter` (default: null)

    A simple function for filtering SDP as part of the peer
    connection handshake (see the Using Filters details below).

  #### Example Usage

  ```js
  var couple = require('rtc/couple');

  couple(pc, '54879965-ce43-426e-a8ef-09ac1e39a16d', signaller);
  ```

  #### Using Filters

  In certain instances you may wish to modify the raw SDP that is provided
  by the `createOffer` and `createAnswer` calls.  This can be done by passing
  a `sdpfilter` function (or array) in the options.  For example:

  ```js
  // run the sdp from through a local tweakSdp function.
  couple(pc, '54879965-ce43-426e-a8ef-09ac1e39a16d', signaller, {
    sdpfilter: tweakSdp
  });
  ```

**/
function couple(pc, targetId, signaller, opts) {
  var debug = require('cog/logger')('couple');

  // create a monitor for the connection
  var mon = monitor(pc);
  var queuedCandidates = [];
  var sdpFilter = (opts || {}).sdpfilter;
  var reactive = (opts || {}).reactive;
  var offerTimeout;
  var endOfCandidates = true;

  // configure the time to wait between receiving a 'disconnect'
  // iceConnectionState and determining that we are closed
  var disconnectTimeout = (opts || {}).disconnectTimeout || 10000;
  var disconnectTimer;

  // if the signaller does not support this isMaster function throw an
  // exception
  if (typeof signaller.isMaster != 'function') {
    throw new Error('rtc-signaller instance >= 0.14.0 required');
  }

  // initilaise the negotiation helpers
  var isMaster = signaller.isMaster(targetId);

  var createOffer = prepNegotiate(
    'createOffer',
    isMaster,
    [ checkStable, checkNotConnecting ]
  );

  var createAnswer = prepNegotiate(
    'createAnswer',
    true,
    [ checkNotConnecting ]
  );

  // initialise the processing queue (one at a time please)
  var q = async.queue(function(task, cb) {
    // if the task has no operation, then trigger the callback immediately
    if (typeof task.op != 'function') {
      return cb();
    }

    // process the task operation
    task.op(task, cb);
  }, 1);

  // initialise session description and icecandidate objects
  var RTCSessionDescription = (opts || {}).RTCSessionDescription ||
    detect('RTCSessionDescription');

  var RTCIceCandidate = (opts || {}).RTCIceCandidate ||
    detect('RTCIceCandidate');

  function abort(stage, sdp, cb) {
    return function(err) {
      // log the error
      console.error('rtc/couple error (' + stage + '): ', err);

      if (typeof cb == 'function') {
        cb(err);
      }
    };
  }

  function applyCandidatesWhenStable() {
    if (pc.signalingState == 'stable' && pc.remoteDescription) {
      debug('signaling state = stable, applying queued candidates');
      mon.removeListener('change', applyCandidatesWhenStable);

      // apply any queued candidates
      queuedCandidates.splice(0).forEach(function(data) {
        debug('applying queued candidate', data);

        try {
          pc.addIceCandidate(new RTCIceCandidate(data));
        }
        catch (e) {
          debug('invalidate candidate specified: ', data);
        }
      });
    }
  }

  function checkNotConnecting(negotiate) {
    if (pc.iceConnectionState != 'checking') {
      return true;
    }

    debug('connection state is checking, will wait to create a new offer');
    mon.once('connected', function() {
      q.push({ op: negotiate });
    });

    return false;
  }

  function checkStable(negotiate) {
    if (pc.signalingState === 'stable') {
      return true;
    }

    debug('cannot create offer, signaling state != stable, will retry');
    mon.on('change', function waitForStable() {
      if (pc.signalingState === 'stable') {
        q.push({ op: negotiate });
      }

      mon.removeListener('change', waitForStable);
    });

    return false;
  }

  function decouple() {
    debug('decoupling ' + signaller.id + ' from ' + targetId);

    // remove local pc event handlers
    pc.onicecandidate = null;
    pc.onnegotiationneeded = null;

    // remove listeners
    signaller.removeListener('sdp', handleSdp);
    signaller.removeListener('candidate', handleRemoteCandidate);
  }

  function prepNegotiate(methodName, allowed, preflightChecks) {
    // ensure we have a valid preflightChecks array
    preflightChecks = [].concat(preflightChecks || []);

    return function negotiate(task, cb) {
      var checksOK = true;

      // if the task is not allowed, then send a negotiate request to our
      // peer
      if (! allowed) {
        signaller.to(targetId).send('/negotiate');
        return cb();
      }

      // run the preflight checks
      preflightChecks.forEach(function(check) {
        checksOK = checksOK && check(negotiate);
      });

      // if the checks have not passed, then abort for the moment
      if (! checksOK) {
        debug('preflight checks did not pass, aborting ' + methodName);
        return cb();
      }

      // create the offer
      debug('calling ' + methodName);
      // debug('gathering state = ' + conn.iceGatheringState);
      // debug('connection state = ' + conn.iceConnectionState);
      // debug('signaling state = ' + conn.signalingState);

      pc[methodName](
        function(desc) {

          // if a filter has been specified, then apply the filter
          if (typeof sdpFilter == 'function') {
            desc.sdp = sdpFilter(desc.sdp, pc, methodName);
          }

          q.push({ op: queueLocalDesc(desc) });
          cb();
        },

        // on error, abort
        abort(methodName, '', cb)
      );
    };
  }

  function handleConnectionClose() {
    debug('captured pc close, iceConnectionState = ' + pc.iceConnectionState);
    decouple();
  }

  function handleDisconnect() {
    debug('captured pc disconnect, monitoring connection status');

    // start the disconnect timer
    disconnectTimer = setTimeout(function() {
      pc.close();
    }, disconnectTimeout);

    mon.on('change', handleDisconnectAbort);
  }

  function handleDisconnectAbort() {
    mon.removeListener('change', handleDisconnectAbort);

    // clear the disconnect timer
    debug('reset disconnect timer, state: ' + pc.iceConnectionState);
    clearTimeout(disconnectTimer);

    // if we have a closed or failed status, then close the connection
    if (CLOSED_STATES.indexOf(pc.iceConnectionState) >= 0) {
      handleConnectionClose();
    }
    // otherwise, reconnect the disconnect monitor
    else {
      mon.once('disconnect', handleDisconnect);
    }
  };

  function handleLocalCandidate(evt) {
    if (evt.candidate) {
      signaller.to(targetId).send('/candidate', evt.candidate);
      endOfCandidates = false;
    }
    else if (! endOfCandidates) {
      endOfCandidates = true;
      debug('ice gathering state complete');
      signaller.to(targetId).send('/endofcandidates', {});
    }
  }

  function handleRemoteCandidate(data, src) {
    if ((! src) || (src.id !== targetId)) {
      return;
    }

    // queue candidates while the signaling state is not stable
    if (pc.signalingState != 'stable' || (! pc.remoteDescription)) {
      queuedCandidates.push(data);

      mon.removeListener('change', applyCandidatesWhenStable);
      mon.on('change', applyCandidatesWhenStable);
      return;
    }

    try {
      pc.addIceCandidate(new RTCIceCandidate(data));
    }
    catch (e) {
      debug('invalidate candidate specified: ', data);
    }
  }

  function handleSdp(data, src) {
    var abortType = data.type === 'offer' ? 'createAnswer' : 'createOffer';

    // if the source is unknown or not a match, then abort
    if ((! src) || (src.id !== targetId)) {
      return;
    }

    // prioritize setting the remote description operation
    q.push({ op: function(task, cb) {
      // update the remote description
      // once successful, send the answer
      pc.setRemoteDescription(
        new RTCSessionDescription(data),

        function() {
          // create the answer
          if (data.type === 'offer') {
            queue(createAnswer)();
          }

          // trigger the callback
          cb();
        },

        abort(abortType, data.sdp, cb)
      );
    }});
  }

  function queue(negotiateTask) {
    return function() {
      q.push([
        { op: negotiateTask }
      ]);
    };
  }

  function queueLocalDesc(desc) {
    return function setLocalDesc(task, cb) {
      debug('setting local description');

      // initialise the local description
      pc.setLocalDescription(
        desc,

        // if successful, then send the sdp over the wire
        function() {
          // send the sdp
          signaller.to(targetId).send('/sdp', desc);

          // callback
          cb();
        },

        // abort('setLocalDesc', desc.sdp, cb)
        // on error, abort
        function(err) {
          debug('error setting local description', err);
          debug(desc.sdp);
          // setTimeout(function() {
          //   setLocalDesc(task, cb, (retryCount || 0) + 1);
          // }, 500);

          cb(err);
        }
      );
    };
  }

  // if the target id is not a string, then complain
  if (typeof targetId != 'string' && (! (targetId instanceof String))) {
    throw new Error('2nd argument (targetId) should be a string');
  }

  // when regotiation is needed look for the peer
  if (reactive) {
    pc.onnegotiationneeded = function() {
      debug('renegotiation required, will create offer in 50ms');
      clearTimeout(offerTimeout);
      offerTimeout = setTimeout(queue(createOffer), 50);
    };
  }

  pc.onicecandidate = handleLocalCandidate;

  // when we receive sdp, then
  signaller.on('sdp', handleSdp);
  signaller.on('candidate', handleRemoteCandidate);

  // if this is a master connection, listen for negotiate events
  if (isMaster) {
    signaller.on('negotiate', function(src) {
      if (src.id === targetId) {
        debug('got negotiate request from ' + targetId + ', creating offer');
        q.push({ op: createOffer });
      }
    });
  }

  // when the connection closes, remove event handlers
  mon.once('closed', handleConnectionClose);
  mon.once('disconnected', handleDisconnect);

  // patch in the create offer functions
  mon.createOffer = queue(createOffer);

  return mon;
}

module.exports = couple;<|endoftext|>