<commit_before>import __polyfill from "babel-polyfill";
import should from 'should';
import Driver from "../isotropy-backend-mongodb";

describe("Isotropy MongoDb Backend", () => {
    const TEST_DB = "isotropy-backend-mongodb-test-A1934";
    const CONN_STR = `mongodb://localhost:27017/${TEST_DB}`;

    beforeEach(async () => {
        const db = await Driver.MongoClient.connect(CONN_STR);
        const collection = db.collection("test");
        await collection.insertOne({a: 1});
        await db.dropDatabase();
    });

    describe("MongoClient", () => {
        it("Connect to a database", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            db.should.not.be.empty();
        });
    });

    describe("Database", () => {
        it("Return a collection", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const count = await collection.count({a: 1});
            count.should.equal(4);
        });

        it("Drop the database", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            db.dropDatabase();
            const newCollection = db.collection("test");
            const count = await newCollection.count({a: 1});
            count.should.equal(0);
        });
    });

    describe("Collection", () => {
        it("Open a database", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            db.should.not.be.empty();
        });

        it("Count the number of items in the collection", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const count = await collection.count({a: 1});
            count.should.equal(4);
        });

        it("Create an index", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.createIndex({a: 1, b: 1});
            await collection.createIndex({b: 1});
            const indexes = await collection.indexes();
            indexes.length.should.equal(3);
        });

        it("Delete an item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.deleteOne({a: 1});
            const count = await collection.count({a: 1});
            count.should.equal(3);
        });

        it("Delete many items", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.deleteMany({a: 1});
            const count = await collection.count({a: 1});
            count.should.equal(0);
        });

        it("Drop a collection", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.drop();
            const newCollection = db.collection("test");
            const count = await newCollection.count({a: 1});
            count.should.equal(0);
        });

        it("Drop all indexes", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.createIndex({a: 1, b: 1});
            await collection.createIndex({b: 1});
            await collection.dropIndexes();
            const indexes = await collection.indexes();
            indexes.length.should.equal(1);
        });

        it("Drop an index", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.createIndex({a: 1, b: 1});
            await collection.createIndex({b: 1});
            await collection.dropIndex("b_1");
            const indexes = await collection.indexes();
            indexes.length.should.equal(2);
        });

        it("Return a cursor", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const result = await cursor.toArray();
            result.length.should.equal(4);
        });

        it("Find one item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1}).limit(1);
            const result = await cursor.next();
            result.a.should.equal(1);
        });

        it("Fetch all indexes", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.createIndex({a: 1, b: 1});
            await collection.createIndex({b: 1});
            const indexes = await collection.indexes();
            indexes.length.should.equal(3);
        });

        it("Insert an item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertOne({a: 1});
            await collection.insertOne({a: 1});
            await collection.insertOne({a: 2});
            const count = await collection.count({a: 1});
            count.should.equal(2);
        });

        it("Insert many items", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const result = await cursor.toArray();
            result.length.should.equal(4);
        });

        it("Update an item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.updateOne({a: 1}, {$set: {a: 20}});
            const cursor = collection.find({a: 20}).limit(1);
            const result = await cursor.next();
            result.a.should.equal(20);
        });

        it("Update many items", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.updateMany({a: 1}, {$set: {a: 20}});
            const cursor = collection.find({a: 20});
            const result = await cursor.toArray();
            result.length.should.equal(4);
        });
    });

    describe("Cursor", () => {
        it("Return an array of results", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const result = await cursor.toArray();
            result.length.should.equal(4);
        });

        it("Count items", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const count = await cursor.count();
            count.should.equal(4);
        });

        it("Check if there is a next item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const hasNext = await cursor.hasNext();
            hasNext.should.be.true();
        });

        it("Limit results", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            cursor.limit(2);
            const result = await cursor.toArray();
            result.length.should.equal(2);
        });

        it("Get the next item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const result = await cursor.next();
            result.a.should.equal(1);
        });

        it("Skip results", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            cursor.skip(1);
            const result = await cursor.toArray();
            result.length.should.equal(3);
        });

        it("Sort results", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.createIndex({a: 1});
            await collection.createIndex({a: -1});
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find();
            var x = cursor.sort([['a', -1]]);
            const result = await x.toArray();
            result[0].a.should.equal(2);
        });
    });
})
<commit_msg>Add test for field selection<commit_after>import __polyfill from "babel-polyfill";
import should from 'should';
import Driver from "../isotropy-backend-mongodb";

describe("Isotropy MongoDb Backend", () => {
    const TEST_DB = "isotropy-backend-mongodb-test-A1934";
    const CONN_STR = `mongodb://localhost:27017/${TEST_DB}`;

    beforeEach(async () => {
        const db = await Driver.MongoClient.connect(CONN_STR);
        const collection = db.collection("test");
        await collection.insertOne({a: 1});
        await db.dropDatabase();
    });

    describe("MongoClient", () => {
        it("Connect to a database", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            db.should.not.be.empty();
        });
    });

    describe("Database", () => {
        it("Return a collection", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const count = await collection.count({a: 1});
            count.should.equal(4);
        });

        it("Drop the database", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            db.dropDatabase();
            const newCollection = db.collection("test");
            const count = await newCollection.count({a: 1});
            count.should.equal(0);
        });
    });

    describe("Collection", () => {
        it("Open a database", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            db.should.not.be.empty();
        });

        it("Count the number of items in the collection", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const count = await collection.count({a: 1});
            count.should.equal(4);
        });

        it("Create an index", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.createIndex({a: 1, b: 1});
            await collection.createIndex({b: 1});
            const indexes = await collection.indexes();
            indexes.length.should.equal(3);
        });

        it("Delete an item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.deleteOne({a: 1});
            const count = await collection.count({a: 1});
            count.should.equal(3);
        });

        it("Delete many items", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.deleteMany({a: 1});
            const count = await collection.count({a: 1});
            count.should.equal(0);
        });

        it("Drop a collection", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.drop();
            const newCollection = db.collection("test");
            const count = await newCollection.count({a: 1});
            count.should.equal(0);
        });

        it("Drop all indexes", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.createIndex({a: 1, b: 1});
            await collection.createIndex({b: 1});
            await collection.dropIndexes();
            const indexes = await collection.indexes();
            indexes.length.should.equal(1);
        });

        it("Drop an index", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.createIndex({a: 1, b: 1});
            await collection.createIndex({b: 1});
            await collection.dropIndex("b_1");
            const indexes = await collection.indexes();
            indexes.length.should.equal(2);
        });

        it("Return a cursor", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const result = await cursor.toArray();
            result.length.should.equal(4);
        });

        it("Find one item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2, x: 20, y: 30}]);
            const cursor = collection.find({a: 2}).limit(1);
            const result = await cursor.next();
            result.a.should.equal(2);
        });

        it("Find speific fields in one item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2, x: 20, y: 30}]);
            const cursor = collection.find({a: 2}, {x: 1, y: 1}).limit(1);
            const result = await cursor.next();
            Object.keys(result).length.should.equal(3);
            result.x.should.equal(20);
            result.y.should.equal(30);
        });

        it("Fetch all indexes", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.createIndex({a: 1, b: 1});
            await collection.createIndex({b: 1});
            const indexes = await collection.indexes();
            indexes.length.should.equal(3);
        });

        it("Insert an item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertOne({a: 1});
            await collection.insertOne({a: 1});
            await collection.insertOne({a: 2});
            const count = await collection.count({a: 1});
            count.should.equal(2);
        });

        it("Insert many items", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const result = await cursor.toArray();
            result.length.should.equal(4);
        });

        it("Update an item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.updateOne({a: 1}, {$set: {a: 20}});
            const cursor = collection.find({a: 20}).limit(1);
            const result = await cursor.next();
            result.a.should.equal(20);
        });

        it("Update many items", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            await collection.updateMany({a: 1}, {$set: {a: 20}});
            const cursor = collection.find({a: 20});
            const result = await cursor.toArray();
            result.length.should.equal(4);
        });
    });

    describe("Cursor", () => {
        it("Return an array of results", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const result = await cursor.toArray();
            result.length.should.equal(4);
        });

        it("Count items", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const count = await cursor.count();
            count.should.equal(4);
        });

        it("Check if there is a next item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const hasNext = await cursor.hasNext();
            hasNext.should.be.true();
        });

        it("Limit results", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            cursor.limit(2);
            const result = await cursor.toArray();
            result.length.should.equal(2);
        });

        it("Get the next item", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            const result = await cursor.next();
            result.a.should.equal(1);
        });

        it("Skip results", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find({a: 1});
            cursor.skip(1);
            const result = await cursor.toArray();
            result.length.should.equal(3);
        });

        it("Sort results", async () => {
            const db = await Driver.MongoClient.connect(CONN_STR);
            const collection = db.collection("test");
            await collection.createIndex({a: 1});
            await collection.createIndex({a: -1});
            await collection.insertMany([{a: 1}, {a: 1}, {a: 1}, {a: 1}, {a: 2}]);
            const cursor = collection.find();
            var x = cursor.sort([['a', -1]]);
            const result = await x.toArray();
            result[0].a.should.equal(2);
        });
    });
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 Google LLC.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt
 * Code distributed by Google as part of this project is also
 * subject to an additional IP rights grant found at
 * http://polymer.github.io/PATENTS.txt
 */

/**
 * @fileoverview
 * This file controls the configuration for the wdio/selenium tests.
 *
 * Mostly, this file should remain untouched. There are some 'debug hints'
 * (marked as such) scattered throughout, and some command-line switches to
 * activate a sane default set of them documented in the top-level README.md.
 */

// modify these config values as needed
const headless = true;
const root = `shells`;

// don't modify after here (in general)
const process = require('process');
const fs = require('fs');
const errorshot = require('wdio-errorshot-reporter');
const request = require('request');
const debug = process.env.npm_config_wdio_debug || process.argv.includes('--wdio-debug=true');

// By default use whitelisted-ips, add headless if debug is enabled
const HEADLESS_ARG = '--headless';
const WHITELISTED_IPS_ARG = '--whitelisted-ips';
const chromeArgs = (headless && !debug) ? [HEADLESS_ARG, WHITELISTED_IPS_ARG] : [WHITELISTED_IPS_ARG];

exports.config = {
  // This port & path are hardcoded to match chromedriver. See
  // wdio-chromedriver-service for more information.
  port: '9515',
  path: '/',
  //
  // ==================
  // Specify Test Files
  // ==================
  // Define which test specs should run. The pattern is relative to the directory
  // from which `wdio` was called. Notice that, if you are calling `wdio` from an
  // NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
  // directory is where your package.json resides, so `wdio` will be called from there.
  //
  specs: [`./${root}/test/specs/**/*.js`],
  // Patterns to exclude.
  exclude: [
    // 'path/to/excluded/files'
  ],
  //
  // ============
  // Capabilities
  // ============
  // Define your capabilities here. WebdriverIO can run multiple capabilities at the same
  // time. Depending on the number of capabilities, WebdriverIO launches several test
  // sessions. Within your capabilities you can overwrite the spec and exclude options in
  // order to group specific specs to a specific capability.
  //
  // First, you can define how many instances should be started at the same time. Let's
  // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
  // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
  // files and you set maxInstances to 10, all spec files will get tested at the same time
  // and 30 processes will get spawned. The property handles how many capabilities
  // from the same test should run tests.
  //
  maxInstances: 10,
  //
  // If you have trouble getting all important capabilities together, check out the
  // Sauce Labs platform configurator - a great tool to configure your capabilities:
  // https://docs.saucelabs.com/reference/platforms-configurator
  //
  capabilities: [
    {
      // maxInstances can get overwritten per capability. So if you have an in-house Selenium
      // grid with only 5 firefox instances available you can make sure that not more than
      // 5 instances get started at a time.
      maxInstances: 5,
      browserName: 'chrome',
      chromeOptions: {
        // debug hint: modify at top of file
        args: chromeArgs
      }
    }
  ],
  // debug hint: uncomment to attach a debugger
  // execArgv: ['--inspect'],
  //
  // ===================
  // Test Configurations
  // ===================
  // Define all options that are relevant for the WebdriverIO instance here
  //
  // By default WebdriverIO commands are executed in a synchronous way using
  // the wdio-sync package. If you still want to run your tests in an async way
  // e.g. using promises you can set the sync option to false.
  sync: true,
  //
  // Level of logging verbosity: silent | verbose | command | data | result | error
  logLevel: 'silent',
  //
  // Enables colors for log output.
  coloredLogs: true,
  //
  // Warns when a deprecated command is used
  deprecationWarnings: true,
  //
  // If you only want to run your tests until a specific amount of tests have failed use
  // bail (default is 0 - don't bail, run all tests).
  bail: 0,
  //
  // Saves a screenshot to a given path if a command fails.
  screenshotPath: `./${root}/test/errorShots/`,
  //
  // Set a base URL in order to shorten url command calls. If your `url` parameter starts
  // with `/`, the base url gets prepended, not including the path portion of your baseUrl.
  // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
  // gets prepended directly.
  baseUrl: 'http://localhost',
  //
  // Default timeout for all waitFor* commands.
  // debug hint: increase this for debugging
  waitforTimeout: debug ? 1000002 : 10002,
  //
  // Default timeout in milliseconds for request
  // if Selenium Grid doesn't send response
  connectionRetryTimeout: 90000,
  //
  // Default request retries count
  connectionRetryCount: 3,
  //
  // Initialize the browser instance with a WebdriverIO plugin. The object should have the
  // plugin name as key and the desired plugin options as properties. Make sure you have
  // the plugin installed before running any tests. The following plugins are currently
  // available:
  // WebdriverCSS: https://github.com/webdriverio/webdrivercss
  // WebdriverRTC: https://github.com/webdriverio/webdriverrtc
  // Browserevent: https://github.com/webdriverio/browserevent
  // plugins: {
  //     webdrivercss: {
  //         screenshotRoot: 'my-shots',
  //         failedComparisonsRoot: 'diffs',
  //         misMatchTolerance: 0.05,
  //         screenWidth: [320,480,640,1024]
  //     },
  //     webdriverrtc: {},
  //     browserevent: {}
  // },
  //
  // Test runner services
  // Services take over a specific job you don't want to take care of. They enhance
  // your test setup with almost no effort. Unlike plugins, they don't add new
  // commands. Instead, they hook themselves up into the test process.
  services: ['chromedriver'],
  // Framework you want to run your specs with.
  // The following are supported: Mocha, Jasmine, and Cucumber
  // see also: http://webdriver.io/guide/testrunner/frameworks.html
  //
  // Make sure you have the wdio adapter package for the specific framework installed
  // before running any tests.
  framework: 'mocha',
  //
  // Test reporter for stdout.
  reporters: ['spec', errorshot],
  reporterOptions: {
    errorshotReporter: {
      // Template for the screenshot name.
      template: '%browser%_%timestamp%_%parent%-%title%'
    }
  },
  //
  // Options to be passed to Mocha.
  // See the full list at http://mochajs.org/
  mochaOpts: {
    ui: 'bdd',
    // debug hint: increase this timeout for debugging
    timeout: debug ? 6000006 : 600006
  },
  //
  // =====
  // Hooks
  // =====
  // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
  // it and to build services around it. You can either apply a single function or an array of
  // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
  // resolved to continue.
  /**
   * Gets executed once before all workers get launched.
   * @param {Object} config wdio configuration object
   * @param {Array.<Object>} capabilities list of capabilities details
   */
  // onPrepare: function (config, capabilities) {
  // },
  /**
   * Gets executed just before initialising the webdriver session and test framework. It allows you
   * to manipulate configurations depending on the capability or spec.
   * @param {Object} config wdio configuration object
   * @param {Array.<Object>} capabilities list of capabilities details
   * @param {Array.<String>} specs List of spec file paths that are to be run
   */
  // beforeSession: function (config, capabilities, specs) {
  // },
  /**
   * Gets executed before test execution begins. At this point you can access to all global
   * variables like `browser`. It is the perfect place to define custom commands.
   * @param {Array.<Object>} capabilities list of capabilities details
   * @param {Array.<String>} specs List of spec file paths that are to be run
   */
  // before: function (capabilities, specs) {
  // },
  /**
   * Runs before a WebdriverIO command gets executed.
   * @param {String} commandName hook command name
   * @param {Array} args arguments that command would receive
   */
  // beforeCommand: function (commandName, args) {
  // },

  /**
   * Hook that gets executed before the suite starts
   * @param {Object} suite suite details
   */
  // beforeSuite: function (suite) {
  // },
  /**
   * Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
   * @param {Object} test test details
   */
  // beforeTest: function (test) {
  // },
  /**
   * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
   * beforeEach in Mocha)
   */
  // beforeHook: function () {
  // },
  /**
   * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
   * afterEach in Mocha)
   */
  // afterHook: function () {
  // },
  /**
   * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
   * @param {Object} test test details
   */
  // afterTest: function (test) {
  // },
  /**
   * Hook that gets executed after the suite has ended
   * @param {Object} suite suite details
   */
  // afterSuite: function (suite) {
  // },

  /**
   * Runs after a WebdriverIO command gets executed
   * @param {String} commandName hook command name
   * @param {Array} args arguments that command would receive
   * @param {Number} result 0 - command success, 1 - command error
   * @param {Object} error error object if any
   */
  // afterCommand: function (commandName, args, result, error) {
  // },
  /**
   * Gets executed after all tests are done. You still have access to all global variables from
   * the test.
   * @param {Number} result 0 - test pass, 1 - test fail
   * @param {Array.<Object>} capabilities list of capabilities details
   * @param {Array.<String>} specs List of spec file paths that ran
   */
  // after: function (result, capabilities, specs) {
  // },
  /**
   * Gets executed right after terminating the webdriver session.
   * @param {Object} config wdio configuration object
   * @param {Array.<Object>} capabilities list of capabilities details
   * @param {Array.<String>} specs List of spec file paths that ran
   */
  // afterSession: function (config, capabilities, specs) {
  // },
  /**
   * Gets executed after all workers got shut down and the process is about to exit.
   * @param {Object} exitCode 0 - success, 1 - fail
   * @param {Object} config wdio configuration object
   * @param {Array.<Object>} capabilities list of capabilities details
   */
  onComplete: async function(exitCode, config, capabilities) {
    if (!process.env.CONTINUOUS_INTEGRATION) return;
    if (!fs.existsSync(exports.config.screenshotPath)) return;

    const uploadEndpoint = 'https://us-central1-arcs-screenshot-uploader.cloudfunctions.net/arcs-screenshot/';

    const screenshots = fs.readdirSync(exports.config.screenshotPath);
    if (screenshots.length) console.log('Uploading screenshots...');
    await Promise.all(screenshots.map(filename => new Promise((resolve, reject) => {
      fs.createReadStream(exports.config.screenshotPath + filename)
          .pipe(request.post(uploadEndpoint + filename, (err, response, body) => {
            if (err) {
              console.error(`Error on uploading screenshot ${filename}:\n`, err);
              reject(new Error());
            } else {
              console.log(`View ${filename} at https://drive.google.com/file/d/${body}/view`);
              resolve();
            }
          }));
    })));
  }
};

if (debug) {
  const capabilities = exports.config.capabilities;
  if (capabilities.length != 1) {
    throw new Error(`New capabilities have been introduced; that's a good thing! But this code needs updating to take that into account.`);
  }
  const chromeOptions = capabilities[0].chromeOptions;
  chromeOptions.args = chromeOptions.args.filter(arg => arg != HEADLESS_ARG);
}
<commit_msg>wdio.conf.js: reflect folder name change (s/test/tests) (#3338)<commit_after>/**
 * @license
 * Copyright 2019 Google LLC.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt
 * Code distributed by Google as part of this project is also
 * subject to an additional IP rights grant found at
 * http://polymer.github.io/PATENTS.txt
 */

/**
 * @fileoverview
 * This file controls the configuration for the wdio/selenium tests.
 *
 * Mostly, this file should remain untouched. There are some 'debug hints'
 * (marked as such) scattered throughout, and some command-line switches to
 * activate a sane default set of them documented in the top-level README.md.
 */

// modify these config values as needed
const headless = true;
const root = `shells`;

// don't modify after here (in general)
const process = require('process');
const fs = require('fs');
const errorshot = require('wdio-errorshot-reporter');
const request = require('request');
const debug = process.env.npm_config_wdio_debug || process.argv.includes('--wdio-debug=true');

// By default use whitelisted-ips, add headless if debug is enabled
const HEADLESS_ARG = '--headless';
const WHITELISTED_IPS_ARG = '--whitelisted-ips';
const chromeArgs = (headless && !debug) ? [HEADLESS_ARG, WHITELISTED_IPS_ARG] : [WHITELISTED_IPS_ARG];

exports.config = {
  // This port & path are hardcoded to match chromedriver. See
  // wdio-chromedriver-service for more information.
  port: '9515',
  path: '/',
  //
  // ==================
  // Specify Test Files
  // ==================
  // Define which test specs should run. The pattern is relative to the directory
  // from which `wdio` was called. Notice that, if you are calling `wdio` from an
  // NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
  // directory is where your package.json resides, so `wdio` will be called from there.
  //
  specs: [`./${root}/tests/specs/**/*.js`],
  // Patterns to exclude.
  exclude: [
    // 'path/to/excluded/files'
  ],
  //
  // ============
  // Capabilities
  // ============
  // Define your capabilities here. WebdriverIO can run multiple capabilities at the same
  // time. Depending on the number of capabilities, WebdriverIO launches several test
  // sessions. Within your capabilities you can overwrite the spec and exclude options in
  // order to group specific specs to a specific capability.
  //
  // First, you can define how many instances should be started at the same time. Let's
  // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
  // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
  // files and you set maxInstances to 10, all spec files will get tested at the same time
  // and 30 processes will get spawned. The property handles how many capabilities
  // from the same test should run tests.
  //
  maxInstances: 10,
  //
  // If you have trouble getting all important capabilities together, check out the
  // Sauce Labs platform configurator - a great tool to configure your capabilities:
  // https://docs.saucelabs.com/reference/platforms-configurator
  //
  capabilities: [
    {
      // maxInstances can get overwritten per capability. So if you have an in-house Selenium
      // grid with only 5 firefox instances available you can make sure that not more than
      // 5 instances get started at a time.
      maxInstances: 5,
      browserName: 'chrome',
      chromeOptions: {
        // debug hint: modify at top of file
        args: chromeArgs
      }
    }
  ],
  // debug hint: uncomment to attach a debugger
  // execArgv: ['--inspect'],
  //
  // ===================
  // Test Configurations
  // ===================
  // Define all options that are relevant for the WebdriverIO instance here
  //
  // By default WebdriverIO commands are executed in a synchronous way using
  // the wdio-sync package. If you still want to run your tests in an async way
  // e.g. using promises you can set the sync option to false.
  sync: true,
  //
  // Level of logging verbosity: silent | verbose | command | data | result | error
  logLevel: 'silent',
  //
  // Enables colors for log output.
  coloredLogs: true,
  //
  // Warns when a deprecated command is used
  deprecationWarnings: true,
  //
  // If you only want to run your tests until a specific amount of tests have failed use
  // bail (default is 0 - don't bail, run all tests).
  bail: 0,
  //
  // Saves a screenshot to a given path if a command fails.
  screenshotPath: `./${root}/tests/errorShots/`,
  //
  // Set a base URL in order to shorten url command calls. If your `url` parameter starts
  // with `/`, the base url gets prepended, not including the path portion of your baseUrl.
  // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
  // gets prepended directly.
  baseUrl: 'http://localhost',
  //
  // Default timeout for all waitFor* commands.
  // debug hint: increase this for debugging
  waitforTimeout: debug ? 1000002 : 10002,
  //
  // Default timeout in milliseconds for request
  // if Selenium Grid doesn't send response
  connectionRetryTimeout: 90000,
  //
  // Default request retries count
  connectionRetryCount: 3,
  //
  // Initialize the browser instance with a WebdriverIO plugin. The object should have the
  // plugin name as key and the desired plugin options as properties. Make sure you have
  // the plugin installed before running any tests. The following plugins are currently
  // available:
  // WebdriverCSS: https://github.com/webdriverio/webdrivercss
  // WebdriverRTC: https://github.com/webdriverio/webdriverrtc
  // Browserevent: https://github.com/webdriverio/browserevent
  // plugins: {
  //     webdrivercss: {
  //         screenshotRoot: 'my-shots',
  //         failedComparisonsRoot: 'diffs',
  //         misMatchTolerance: 0.05,
  //         screenWidth: [320,480,640,1024]
  //     },
  //     webdriverrtc: {},
  //     browserevent: {}
  // },
  //
  // Test runner services
  // Services take over a specific job you don't want to take care of. They enhance
  // your test setup with almost no effort. Unlike plugins, they don't add new
  // commands. Instead, they hook themselves up into the test process.
  services: ['chromedriver'],
  // Framework you want to run your specs with.
  // The following are supported: Mocha, Jasmine, and Cucumber
  // see also: http://webdriver.io/guide/testrunner/frameworks.html
  //
  // Make sure you have the wdio adapter package for the specific framework installed
  // before running any tests.
  framework: 'mocha',
  //
  // Test reporter for stdout.
  reporters: ['spec', errorshot],
  reporterOptions: {
    errorshotReporter: {
      // Template for the screenshot name.
      template: '%browser%_%timestamp%_%parent%-%title%'
    }
  },
  //
  // Options to be passed to Mocha.
  // See the full list at http://mochajs.org/
  mochaOpts: {
    ui: 'bdd',
    // debug hint: increase this timeout for debugging
    timeout: debug ? 6000006 : 600006
  },
  //
  // =====
  // Hooks
  // =====
  // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
  // it and to build services around it. You can either apply a single function or an array of
  // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
  // resolved to continue.
  /**
   * Gets executed once before all workers get launched.
   * @param {Object} config wdio configuration object
   * @param {Array.<Object>} capabilities list of capabilities details
   */
  // onPrepare: function (config, capabilities) {
  // },
  /**
   * Gets executed just before initialising the webdriver session and test framework. It allows you
   * to manipulate configurations depending on the capability or spec.
   * @param {Object} config wdio configuration object
   * @param {Array.<Object>} capabilities list of capabilities details
   * @param {Array.<String>} specs List of spec file paths that are to be run
   */
  // beforeSession: function (config, capabilities, specs) {
  // },
  /**
   * Gets executed before test execution begins. At this point you can access to all global
   * variables like `browser`. It is the perfect place to define custom commands.
   * @param {Array.<Object>} capabilities list of capabilities details
   * @param {Array.<String>} specs List of spec file paths that are to be run
   */
  // before: function (capabilities, specs) {
  // },
  /**
   * Runs before a WebdriverIO command gets executed.
   * @param {String} commandName hook command name
   * @param {Array} args arguments that command would receive
   */
  // beforeCommand: function (commandName, args) {
  // },

  /**
   * Hook that gets executed before the suite starts
   * @param {Object} suite suite details
   */
  // beforeSuite: function (suite) {
  // },
  /**
   * Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
   * @param {Object} test test details
   */
  // beforeTest: function (test) {
  // },
  /**
   * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
   * beforeEach in Mocha)
   */
  // beforeHook: function () {
  // },
  /**
   * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
   * afterEach in Mocha)
   */
  // afterHook: function () {
  // },
  /**
   * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
   * @param {Object} test test details
   */
  // afterTest: function (test) {
  // },
  /**
   * Hook that gets executed after the suite has ended
   * @param {Object} suite suite details
   */
  // afterSuite: function (suite) {
  // },

  /**
   * Runs after a WebdriverIO command gets executed
   * @param {String} commandName hook command name
   * @param {Array} args arguments that command would receive
   * @param {Number} result 0 - command success, 1 - command error
   * @param {Object} error error object if any
   */
  // afterCommand: function (commandName, args, result, error) {
  // },
  /**
   * Gets executed after all tests are done. You still have access to all global variables from
   * the test.
   * @param {Number} result 0 - test pass, 1 - test fail
   * @param {Array.<Object>} capabilities list of capabilities details
   * @param {Array.<String>} specs List of spec file paths that ran
   */
  // after: function (result, capabilities, specs) {
  // },
  /**
   * Gets executed right after terminating the webdriver session.
   * @param {Object} config wdio configuration object
   * @param {Array.<Object>} capabilities list of capabilities details
   * @param {Array.<String>} specs List of spec file paths that ran
   */
  // afterSession: function (config, capabilities, specs) {
  // },
  /**
   * Gets executed after all workers got shut down and the process is about to exit.
   * @param {Object} exitCode 0 - success, 1 - fail
   * @param {Object} config wdio configuration object
   * @param {Array.<Object>} capabilities list of capabilities details
   */
  onComplete: async function(exitCode, config, capabilities) {
    if (!process.env.CONTINUOUS_INTEGRATION) return;
    if (!fs.existsSync(exports.config.screenshotPath)) return;

    const uploadEndpoint = 'https://us-central1-arcs-screenshot-uploader.cloudfunctions.net/arcs-screenshot/';

    const screenshots = fs.readdirSync(exports.config.screenshotPath);
    if (screenshots.length) console.log('Uploading screenshots...');
    await Promise.all(screenshots.map(filename => new Promise((resolve, reject) => {
      fs.createReadStream(exports.config.screenshotPath + filename)
          .pipe(request.post(uploadEndpoint + filename, (err, response, body) => {
            if (err) {
              console.error(`Error on uploading screenshot ${filename}:\n`, err);
              reject(new Error());
            } else {
              console.log(`View ${filename} at https://drive.google.com/file/d/${body}/view`);
              resolve();
            }
          }));
    })));
  }
};

if (debug) {
  const capabilities = exports.config.capabilities;
  if (capabilities.length != 1) {
    throw new Error(`New capabilities have been introduced; that's a good thing! But this code needs updating to take that into account.`);
  }
  const chromeOptions = capabilities[0].chromeOptions;
  chromeOptions.args = chromeOptions.args.filter(arg => arg != HEADLESS_ARG);
}
<|endoftext|>


<commit_before>function ActivityPlot(target, team_id, problem_id, update) {
    var self = this;

    self.update = typeof update !== 'undefined' ? update : true;

    self.target = target;
    self.team_id = team_id;
    self.problem_id = problem_id;
    self.source_url = 'activity_data_source.php';
    self.target.data("activity_plot", self);
    self.update_interval = 5 * 1000;
    var data = get_json_synchronous("icpc/common_data.php");
    self.BALLOON_COLORS = data['BALLOON_COLORS'];
    self.TEAMS= data['TEAMS'];

    self.plot = function(response) {
        var problems_used = response.problems_used;
        var num_problems = problems_used.length;
        var problem_height = response.max_problems_per_bin;
        var plot_height = num_problems * problem_height;
        var minY = - problem_height / 4;
        var maxY = plot_height + problem_height / 4;

        var ticks = [];
        var colors = [];

        for (var i = 0; i < num_problems; ++i) {
            var pid = problems_used[i].toUpperCase();
            var href = set_query_field(window.location.href, 'problem_id', pid);
            ticks.push([i * problem_height, '' + pid + '']);
            colors.push(self.BALLOON_COLORS[pid]);
        }

        var options = {
            colors: colors,
            grid: { hoverable: true, clickable: true, },
            yaxis: { ticks: ticks,
                zoomRange: [maxY, maxY], // do not allow zooming in on the y-axis
                panRange: [maxY, maxY], // do not allow panning on the y-axis
                min: minY,
                max: maxY,
            },
            xaxis: {
                zoomRange: [10, 321], // extra space to allow for the legend
                panRange: [0, 320], // extra space to allow for the legend
                min: 0,
                max: 320, // extra space to allow for the legend
                tickDecimals: 0,
            },
            zoom: { interactive: true,  },
            pan: { interactive: true,  },
        }

        // if we are already zoomed, preserve the zoom
        if (self.flot) {
            var zoom = self.flot.getAxes();
            options.xaxis.min = zoom.xaxis.min;
            options.xaxis.max = zoom.xaxis.max;
            options.yaxis.min = zoom.yaxis.min;
            options.yaxis.max = zoom.yaxis.max;
        }

        self.flot = $.plot(self.target, response.flot_data, options);
    }

    self.showInfo = function(x, y, contents) {
        $("
" + contents + "
").css(
            {
                position: 'absolute',
                top: y + 10,
                left: x + 10,
                border: '1px solid green',
                padding: '2px',
                'background-color': '#efe',
                opacity: 0.90
            }
        ).appendTo(target);
    }

    self.updatePlot = function() {
        var query = [];
        if (self.team_id) { query.push("team_id=" + self.team_id); }
        if (self.problem_id) { query.push("problem_id=" + self.problem_id); }
        if (query) { query = "?" + query.join("&"); }
        var url = self.source_url + query;
        $.ajax({
            url: url,
            data: { team_id: self.team_id },
            success: self.plot,
            error: function(jqXHR, err) { console.log("updatePlot failed for " + url + ": " + jqXHR + ", " + err); },
            dataType: "json"
        });

        if (self.update) {
            setTimeout(self.updatePlot, self.update_interval);
        }
    }

    self.initUI = function() {
        self.target.bind("plothover",
            function(evt, pos, item) {
                if (item) {
                    self.target.find("div.hoverinfo").remove();
                    var content = '(unknown)';
                    if (item.series && item.series.submissionInfo) {
                        // for the submissions
                        var school_name = 'UNKNOWN';
                        try {
                            school_name = self.TEAMS[item.series.submissionInfo[item.dataIndex].team_id]['school_name'];
                        } catch (e) {}
                        content = item.series.label + ": "
                                + school_name +
                                  " problem " + item.series.submissionInfo[item.dataIndex].problem_id +
                                  " at time " + item.datapoint[0] +
                                  " (" + item.series.submissionInfo[item.dataIndex].lang_id + ")";
                    } else {
                        // for the edits
                        var numEdits = item.datapoint[1] - item.datapoint[2];
                        var edit = numEdits == 1 ? "team edited" : "teams edited";
                        content = numEdits + " " + edit;
                    }
                    // move the tooltip to a position relative to the plot container
                    var offset = self.target.offset();
                    self.showInfo(item.pageX - offset.left, item.pageY - offset.top, content);
                }
            }
        );

        self.target.bind("plotclick",
            function(evt, pos, item) {
                if (item && item.series && item.series.submissionInfo) {
                    var new_location = null;
                    if (self.team_id && /^[0-9]+$/.test(self.team_id)) {
                        new_location = '/domjudge/jury/submission.php?ext_id=' + item.series.submissionInfo[item.dataIndex].submission_id;
                    } else {
                        new_location = add_query_field(window.location.href, 'team_id', item.series.submissionInfo[item.dataIndex].team_id);
                    }
                    if (new_location) {
                        window.location.assign(new_location);
                    }
                }
            }
        );
    }

    $(function() {
        self.initUI();
        self.updatePlot();
    });
}
<commit_msg>add ability to select when activity graph is clickable<commit_after>function ActivityPlot(target, team_id, problem_id, update, clickable) {
    var self = this;

    self.update = typeof update !== 'undefined' ? update : true;
    self.clickable = typeof clickable !== 'undefined' ? clickable : true;

    self.target = target;
    self.team_id = team_id;
    self.problem_id = problem_id;
    self.source_url = 'activity_data_source.php';
    self.target.data("activity_plot", self);
    self.update_interval = 5 * 1000;
    var data = get_json_synchronous("icpc/common_data.php");
    self.BALLOON_COLORS = data['BALLOON_COLORS'];
    self.TEAMS= data['TEAMS'];

    self.plot = function(response) {
        var problems_used = response.problems_used;
        var num_problems = problems_used.length;
        var problem_height = response.max_problems_per_bin;
        var plot_height = num_problems * problem_height;
        var minY = - problem_height / 4;
        var maxY = plot_height + problem_height / 4;

        var ticks = [];
        var colors = [];

        for (var i = 0; i < num_problems; ++i) {
            var pid = problems_used[i].toUpperCase();
            var href = set_query_field(window.location.href, 'problem_id', pid);
            ticks.push([i * problem_height, '' + pid + '']);
            colors.push(self.BALLOON_COLORS[pid]);
        }

        var options = {
            colors: colors,
            grid: { hoverable: true, clickable: self.clickable, },
            yaxis: { ticks: ticks,
                zoomRange: [maxY, maxY], // do not allow zooming in on the y-axis
                panRange: [maxY, maxY], // do not allow panning on the y-axis
                min: minY,
                max: maxY,
            },
            xaxis: {
                zoomRange: [10, 321], // extra space to allow for the legend
                panRange: [0, 320], // extra space to allow for the legend
                min: 0,
                max: 320, // extra space to allow for the legend
                tickDecimals: 0,
            },
            zoom: { interactive: true,  },
            pan: { interactive: true,  },
        }

        // if we are already zoomed, preserve the zoom
        if (self.flot) {
            var zoom = self.flot.getAxes();
            options.xaxis.min = zoom.xaxis.min;
            options.xaxis.max = zoom.xaxis.max;
            options.yaxis.min = zoom.yaxis.min;
            options.yaxis.max = zoom.yaxis.max;
        }

        self.flot = $.plot(self.target, response.flot_data, options);
    }

    self.showInfo = function(x, y, contents) {
        $("
" + contents + "
").css(
            {
                position: 'absolute',
                top: y + 10,
                left: x + 10,
                border: '1px solid green',
                padding: '2px',
                'background-color': '#efe',
                opacity: 0.90
            }
        ).appendTo(target);
    }

    self.updatePlot = function() {
        var query = [];
        if (self.team_id) { query.push("team_id=" + self.team_id); }
        if (self.problem_id) { query.push("problem_id=" + self.problem_id); }
        if (query) { query = "?" + query.join("&"); }
        var url = self.source_url + query;
        $.ajax({
            url: url,
            data: { team_id: self.team_id },
            success: self.plot,
            error: function(jqXHR, err) { console.log("updatePlot failed for " + url + ": " + jqXHR + ", " + err); },
            dataType: "json"
        });

        if (self.update) {
            setTimeout(self.updatePlot, self.update_interval);
        }
    }

    self.initUI = function() {
        self.target.bind("plothover",
            function(evt, pos, item) {
                if (item) {
                    self.target.find("div.hoverinfo").remove();
                    var content = '(unknown)';
                    if (item.series && item.series.submissionInfo) {
                        // for the submissions
                        var school_name = 'UNKNOWN';
                        try {
                            school_name = self.TEAMS[item.series.submissionInfo[item.dataIndex].team_id]['school_name'];
                        } catch (e) {}
                        content = item.series.label + ": "
                                + school_name +
                                  " problem " + item.series.submissionInfo[item.dataIndex].problem_id +
                                  " at time " + item.datapoint[0] +
                                  " (" + item.series.submissionInfo[item.dataIndex].lang_id + ")";
                    } else {
                        // for the edits
                        var numEdits = item.datapoint[1] - item.datapoint[2];
                        var edit = numEdits == 1 ? "team edited" : "teams edited";
                        content = numEdits + " " + edit;
                    }
                    // move the tooltip to a position relative to the plot container
                    var offset = self.target.offset();
                    self.showInfo(item.pageX - offset.left, item.pageY - offset.top, content);
                }
            }
        );

        self.target.bind("plotclick",
            function(evt, pos, item) {
                if (item && item.series && item.series.submissionInfo) {
                    var new_location = null;
                    if (self.team_id && /^[0-9]+$/.test(self.team_id)) {
                        new_location = '/domjudge/jury/submission.php?ext_id=' + item.series.submissionInfo[item.dataIndex].submission_id;
                    } else {
                        new_location = set_query_field(window.location.href, 'team_id', item.series.submissionInfo[item.dataIndex].team_id);
                    }
                    if (new_location) {
                        window.location.assign(new_location);
                    }
                }
            }
        );
    }

    $(function() {
        self.initUI();
        self.updatePlot();
    });
}
<|endoftext|>


<commit_before>function ActivityPlot(target, team_id, problem_id, update) {
    var self = this;

    self.update = typeof update !== 'undefined' ? update : true;

    self.target = target;
    self.team_id = team_id;
    self.problem_id = problem_id;
    self.source_url = 'activity_data_source.php';
    self.target.data("activity_plot", self);
    self.update_interval = 5 * 1000;
    var data = get_json_synchronous("icpc/common_data.php");
    self.BALLOON_COLORS = data['BALLOON_COLORS'];
    self.TEAMS= data['TEAMS'];

    self.plot = function(response) {
        var problems_used = response.problems_used;
        var num_problems = problems_used.length;
        var problem_height = response.max_problems_per_bin;
        var plot_height = num_problems * problem_height;
        var minY = - problem_height / 4;
        var maxY = plot_height + problem_height / 4;

        var ticks = [];
        var colors = [];

        for (var i = 0; i < num_problems; ++i) {
            var pid = problems_used[i].toUpperCase();
            var href = set_query_field(window.location.href, 'problem_id', pid);
            ticks.push([i * problem_height, '' + pid + '']);
            colors.push(self.BALLOON_COLORS[pid]);
        }

        var options = {
            colors: colors,
            grid: { hoverable: true, clickable: true, },
            yaxis: { ticks: ticks,
                zoomRange: [maxY, maxY], // do not allow zooming in on the y-axis
                panRange: [maxY, maxY], // do not allow panning on the y-axis
                min: minY,
                max: maxY,
            },
            xaxis: {
                zoomRange: [10, 321], // extra space to allow for the legend
                panRange: [0, 320], // extra space to allow for the legend
                min: 0,
                max: 320, // extra space to allow for the legend
                tickDecimals: 0,
            },
            zoom: { interactive: true,  },
            pan: { interactive: true,  },
        }

        // if we are already zoomed, preserve the zoom
        if (self.flot) {
            var zoom = self.flot.getAxes();
            options.xaxis.min = zoom.xaxis.min;
            options.xaxis.max = zoom.xaxis.max;
            options.yaxis.min = zoom.yaxis.min;
            options.yaxis.max = zoom.yaxis.max;
        }

        self.flot = $.plot(self.target, response.flot_data, options);
    }

    self.showInfo = function(x, y, contents) {
        $("
" + contents + "
").css(
            {
                position: 'absolute',
                top: y + 10,
                left: x + 10,
                border: '1px solid green',
                padding: '2px',
                'background-color': '#efe',
                opacity: 0.90
            }
        ).appendTo(target);
    }

    self.updatePlot = function() {
        var query = [];
        if (self.team_id) { query.push("team_id=" + self.team_id); }
        if (self.problem_id) { query.push("problem_id=" + self.problem_id); }
        if (query) { query = "?" + query.join("&"); }
        var url = self.source_url + query;
        $.ajax({
            url: url,
            data: { team_id: self.team_id },
            success: self.plot,
            error: function(jqXHR, err) { console.log("updatePlot failed for " + url + ": " + jqXHR + ", " + err); },
            dataType: "json"
        });

        if (self.update) {
            setTimeout(self.updatePlot, self.update_interval);
        }
    }

    self.initUI = function() {
        self.target.bind("plothover",
            function(evt, pos, item) {
                if (item) {
                    self.target.find("div.hoverinfo").remove();
                    var content = '(unknown)';
                    if (item.series && item.series.submissionInfo) {
                        // for the submissions
                        var school_name = 'UNKNOWN';
                        try {
                            school_name = self.TEAMS[item.series.submissionInfo[item.dataIndex].team_id]['school_name'];
                        } catch (e) {}
                        content = item.series.label + ": "
                                + school_name +
                                  " problem " + item.series.submissionInfo[item.dataIndex].problem_id +
                                  " at time " + item.datapoint[0] +
                                  " (" + item.series.submissionInfo[item.dataIndex].lang_id + ")";
                    } else {
                        // for the edits
                        var numEdits = item.datapoint[1] - item.datapoint[2];
                        var edit = numEdits == 1 ? "team edited" : "teams edited";
                        content = numEdits + " " + edit;
                    }
                    // move the tooltip to a position relative to the plot container
                    var offset = self.target.offset();
                    self.showInfo(item.pageX - offset.left, item.pageY - offset.top, content);
                }
            }
        );

        self.target.bind("plotclick",
            function(evt, pos, item) {
                if (item && item.series && item.series.submissionInfo) {
                    var new_location = add_query_field(window.location.href, 'team_id', item.series.submissionInfo[item.dataIndex].team_id);
                    window.location.assign(new_location);
                }
            }
        );
    }

    $(function() {
        self.initUI();
        self.updatePlot();
    });
}
<commit_msg>link to submission if we are already in a team view<commit_after>function ActivityPlot(target, team_id, problem_id, update) {
    var self = this;

    self.update = typeof update !== 'undefined' ? update : true;

    self.target = target;
    self.team_id = team_id;
    self.problem_id = problem_id;
    self.source_url = 'activity_data_source.php';
    self.target.data("activity_plot", self);
    self.update_interval = 5 * 1000;
    var data = get_json_synchronous("icpc/common_data.php");
    self.BALLOON_COLORS = data['BALLOON_COLORS'];
    self.TEAMS= data['TEAMS'];

    self.plot = function(response) {
        var problems_used = response.problems_used;
        var num_problems = problems_used.length;
        var problem_height = response.max_problems_per_bin;
        var plot_height = num_problems * problem_height;
        var minY = - problem_height / 4;
        var maxY = plot_height + problem_height / 4;

        var ticks = [];
        var colors = [];

        for (var i = 0; i < num_problems; ++i) {
            var pid = problems_used[i].toUpperCase();
            var href = set_query_field(window.location.href, 'problem_id', pid);
            ticks.push([i * problem_height, '' + pid + '']);
            colors.push(self.BALLOON_COLORS[pid]);
        }

        var options = {
            colors: colors,
            grid: { hoverable: true, clickable: true, },
            yaxis: { ticks: ticks,
                zoomRange: [maxY, maxY], // do not allow zooming in on the y-axis
                panRange: [maxY, maxY], // do not allow panning on the y-axis
                min: minY,
                max: maxY,
            },
            xaxis: {
                zoomRange: [10, 321], // extra space to allow for the legend
                panRange: [0, 320], // extra space to allow for the legend
                min: 0,
                max: 320, // extra space to allow for the legend
                tickDecimals: 0,
            },
            zoom: { interactive: true,  },
            pan: { interactive: true,  },
        }

        // if we are already zoomed, preserve the zoom
        if (self.flot) {
            var zoom = self.flot.getAxes();
            options.xaxis.min = zoom.xaxis.min;
            options.xaxis.max = zoom.xaxis.max;
            options.yaxis.min = zoom.yaxis.min;
            options.yaxis.max = zoom.yaxis.max;
        }

        self.flot = $.plot(self.target, response.flot_data, options);
    }

    self.showInfo = function(x, y, contents) {
        $("
" + contents + "
").css(
            {
                position: 'absolute',
                top: y + 10,
                left: x + 10,
                border: '1px solid green',
                padding: '2px',
                'background-color': '#efe',
                opacity: 0.90
            }
        ).appendTo(target);
    }

    self.updatePlot = function() {
        var query = [];
        if (self.team_id) { query.push("team_id=" + self.team_id); }
        if (self.problem_id) { query.push("problem_id=" + self.problem_id); }
        if (query) { query = "?" + query.join("&"); }
        var url = self.source_url + query;
        $.ajax({
            url: url,
            data: { team_id: self.team_id },
            success: self.plot,
            error: function(jqXHR, err) { console.log("updatePlot failed for " + url + ": " + jqXHR + ", " + err); },
            dataType: "json"
        });

        if (self.update) {
            setTimeout(self.updatePlot, self.update_interval);
        }
    }

    self.initUI = function() {
        self.target.bind("plothover",
            function(evt, pos, item) {
                if (item) {
                    self.target.find("div.hoverinfo").remove();
                    var content = '(unknown)';
                    if (item.series && item.series.submissionInfo) {
                        // for the submissions
                        var school_name = 'UNKNOWN';
                        try {
                            school_name = self.TEAMS[item.series.submissionInfo[item.dataIndex].team_id]['school_name'];
                        } catch (e) {}
                        content = item.series.label + ": "
                                + school_name +
                                  " problem " + item.series.submissionInfo[item.dataIndex].problem_id +
                                  " at time " + item.datapoint[0] +
                                  " (" + item.series.submissionInfo[item.dataIndex].lang_id + ")";
                    } else {
                        // for the edits
                        var numEdits = item.datapoint[1] - item.datapoint[2];
                        var edit = numEdits == 1 ? "team edited" : "teams edited";
                        content = numEdits + " " + edit;
                    }
                    // move the tooltip to a position relative to the plot container
                    var offset = self.target.offset();
                    self.showInfo(item.pageX - offset.left, item.pageY - offset.top, content);
                }
            }
        );

        self.target.bind("plotclick",
            function(evt, pos, item) {
                if (item && item.series && item.series.submissionInfo) {
                    var new_location = null;
                    if (self.team_id && /^[0-9]+$/.test(self.team_id)) {
                        console.log("this is for a particular team!");
                        new_location = '/domjudge/jury/submission.php?ext_id=' + item.series.submissionInfo.submission_id;
                    } else {
                        new_location = add_query_field(window.location.href, 'team_id', item.series.submissionInfo[item.dataIndex].team_id);
                    }
                    if (new_location) {
                        window.location.assign(new_location);
                    }
                }
            }
        );
    }

    $(function() {
        self.initUI();
        self.updatePlot();
    });
}
<|endoftext|>


<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict'

let firewalla = require('./Firewalla.js');

let BitBridge = require('../extension/bitbridge/bitbridge.js')

var spawn = require('child_process').spawn;

let spawnProcess = null;

let spoofLogFile = firewalla.getLogFolder() + "/spoof.log";

let SysManager = require("./SysManager.js");
let sysManager = new SysManager();

let Promise = require('bluebird');

const async = require('asyncawait/async')
const await = require('asyncawait/await')

const iptool = require('ip')

const rclient = require('../util/redis_manager.js').getRedisClient()
const sclient = require('../util/redis_manager.js').getSubscriptionClient()

let log = require("./logger.js")(__filename, 'info');

let fs = require('fs');

let cp = require('child_process');

let monitoredKey = "monitored_hosts";
let unmonitoredKey = "unmonitored_hosts";
const unmonitoredKeyAll = "unmonitored_hosts_all";
let monitoredKey6 = "monitored_hosts6";
let unmonitoredKey6 = "unmonitored_hosts6";
const sem = require('../sensor/SensorEventManager.js').getInstance();

let HostTool = require('../net2/HostTool')
let hostTool = new HostTool();
const fc = require('./config.js')

let exec = require('child-process-promise').exec

let instance = null;

module.exports = class SpooferManager {
  constructor() {
    if (!instance) {
      this.spoofStarted = false;
      this.registeredSpoofInstances = {};
      (async () => {
        const gatewayIp = sysManager.myGateway();
        this.gatewayMac = await hostTool.getMacByIP(gatewayIp);
      })();

      if (firewalla.isMain()) {
        sem.on("DeviceUpdate", (event) => {
          const host = event.host;
          if (sysManager.myGateway6() && this.gatewayMac && host.mac === this.gatewayMac
            && host.ipv6Addr && sysManager.myDNS().includes(sysManager.myGateway())) {
            // v4 dns includes gateway ip, very likely gateway's v6 addresses are dns servers, need to spoof these addresses (no matter public or linklocal)
            log.info("Router also acts as dns, spoof all router's v6 addresses: ", host.ipv6Addr);
            for (let i in host.ipv6Addr) {
              const addr = host.ipv6Addr[i];
              this.registerSpoofInstance(sysManager.monitoringInterface().name, addr, sysManager.myIp6(), true);
            }
          }
        });

        sclient.subscribe("System:IPChange")

        sclient.on("message", (channel, message) => {
          switch (channel) {
            case "System:IPChange":
              this.registerSpoofInstance(sysManager.monitoringInterface().name, sysManager.myGateway(), sysManager.myIp(), false);
              if (sysManager.myGateway6()) {
                this.registerSpoofInstance(sysManager.monitoringInterface().name, sysManager.myGateway6(), sysManager.myIp6()[0], true);
              }
              break;
            default:
          }
        });

        // feature change listener
        (async () => {
          let ipv6Default = false;
          if (firewalla.isBeta() || firewalla.isAlpha() || firewalla.isDevelopmentVersion()) {
            ipv6Default = true;
          }
          if(fc.isFeatureOn("ipv6", ipv6Default)) {
            await this.ipv6On();
          } else {
            await this.ipv6Off();
          }
          fc.onFeature("ipv6", (feature, status) => {
            if(feature != "ipv6")
              return

            if(status) {
              this.ipv6On()
            } else {
              this.ipv6Off()
            }
          })
        })()
      }
      instance = this;
    }
    return instance;
  }

  registerSpoofInstance(intf, routerIP, selfIP, isV6) {
    const key = this._getSpoofInstanceKey(intf, routerIP, selfIP, isV6);
    if (!key)
      return;

    if (!this.registeredSpoofInstances[key]) {
      this.registeredSpoofInstances[key] = BitBridge.createInstance(intf, routerIP, selfIP, isV6);
      if (this.spoofStarted) {
        this.registeredSpoofInstances[key].start();
      }
    } else {
      const oldInstance = this.registeredSpoofInstances[key];
      const newInstance = BitBridge.createInstance(intf, routerIP, selfIP, isV6);
      if (newInstance !== oldInstance) {
        // need to deregister old instance
        this.deregisterSpoofInstance(intf, routerIP, selfIP, isV6);
        this.registeredSpoofInstances[key] = newInstance;
        if (this.spoofStarted) {
          newInstance.start();
        }
      }
    }
  }

  deregisterSpoofInstance(intf, routerIP, selfIP, isV6) {
    const key = this._getSpoofInstanceKey(intf, routerIP, selfIP, isV6);
    if (!key)
      return;

    if (this.registeredSpoofInstances[key]) {
      const spoofInstance = this.registeredSpoofInstances[key];
      if (this.spoofStarted) {
        spoofInstance.stop();
      }
      delete this.registeredSpoofInstances[key];
    }
  }

  _getSpoofInstanceKey(intf, routerIP, selfIP, isV6) {
    isV6 = isV6 || false;
    if (!routerIP) {
      log.error("Cannot create bitbridge instance. Router IP should be specified.");
      return null;
    }
    if (!selfIP && !isV6) {
      log.error("Cannot create bitbridge instance. Self IP should be specified for ipv4.");
      return null;
    }
    intf = intf || "eth0";
    if (isV6) {
      return `${intf}_v6_${routerIP}`;
    } else {
      return `${intf}_v4_${routerIP}`;
    }
  }

  async ipv6On() {
    try {
      await exec("touch /home/pi/.firewalla/config/enablev6");
      await exec("pgrep -x bitbridge6 && sudo pkill bitbridge6; true");
    } catch(err) {
      log.warn("Error when turn on ipv6", err);
    }
  }

  async ipv6Off() {
    try {
      await exec("rm -f /home/pi/.firewalla/config/enablev6");
      await exec("pgrep -x bitbridge6 && sudo pkill bitbridge6; true");
    } catch(err) {
      log.warn("Error when turn off ipv6", err);
    }
  }

  // WORKAROUND VERSION HERE, will move to a better place
  async startSpoofing() {

    if(this.spoofStarted) {
      return;
    }

    log.info("start spoofing")

    await this.emptySpoofSet(); // all monitored_hosts* keys are cleared during startup

    await BitBridge.cleanupSpoofInstanceConfigs(); // cleanup Bitbridge config files (*.rc)

    for (let key in this.registeredSpoofInstances) {
      const spoofInstance = this.registeredSpoofInstances[key];
      spoofInstance.start();
    }

    /*
    let ifName = sysManager.monitoringInterface().name;
    let routerIP = sysManager.myGateway();
    let myIP = sysManager.myIp();
    let gateway6 = sysManager.myGateway6();

    if(!ifName || !myIP || !routerIP) {
      return Promise.reject("require valid interface name, ip address and gateway ip address");
    }

    let b7 = new BitBridge(ifName, routerIP, myIP,null,null,gateway6)
    b7.start()
    */

    this.spoofStarted = true;
  }

  async stopSpoofing() {
    try {
      this.spoofStarted = false;

      for (let key in this.registeredSpoofInstances) {
        const spoofInstance = this.registeredSpoofInstances[key];
        await spoofInstance.stop();
      }
      /*
      let ifName = sysManager.monitoringInterface().name;
      let routerIP = sysManager.myGateway();
      let myIP = sysManager.myIp();

      if(!ifName || !myIP || !routerIP) {
        return Promise.reject("require valid interface name, ip address and gateway ip address");
      }

      let b7 = new BitBridge(ifName, routerIP, myIP)
      b7.stop()
        .then(() => {
          resolve()
        })
      */
    } catch (err) {
      //catch everything here
      log.error("Failed to stop spoofing:", err);
    }
  }

  directSpoof(ip) {
    return async(() => {
      if(iptool.isV4Format(ip)) {
        await (rclient.saddAsync(monitoredKey, ip))
      } else if(iptool.isV6Format(ip)) {
        await (rclient.saddAsync(monitoredKey6, ip))
      } else {
        return Promise.reject(new Error("Invalid ip address: " + ip))
      }
    })()
  }

  emptySpoofSet() {
    return async(() => {
      // clean up redis key
      await (rclient.delAsync(monitoredKey))
      await (rclient.delAsync(unmonitoredKey))
      await (rclient.delAsync(unmonitoredKeyAll))
      await (rclient.delAsync(monitoredKey6))
      await (rclient.delAsync(unmonitoredKey6))
    })()
  }

  loadManualSpoof(mac) {
    return async(() => {
      let key = hostTool.getMacKey(mac)
      let host = await (rclient.hgetallAsync(key))
      let manualSpoof = (host.manualSpoof === '1' ? true : false)
      if(manualSpoof) {
        await (rclient.saddAsync(monitoredKey, host.ipv4Addr))
        await (rclient.sremAsync(unmonitoredKey, host.ipv4Addr))
        await (rclient.sremAsync(unmonitoredKeyAll, host.ipv4Addr))
      } else {
        await (rclient.sremAsync(monitoredKey, host.ipv4Addr))
        await (rclient.saddAsync(unmonitoredKey, host.ipv4Addr))
        await (rclient.saddAsync(unmonitoredKeyAll, host.ipv4Addr))
        setTimeout(() => {
          rclient.sremAsync(unmonitoredKey, host.ipv4Addr)
        }, 8 * 1000) // remove ip from unmonitoredKey after 8 seconds to reduce battery cost of unmonitored devices
      }
    })()
  }

  loadManualSpoofs(hostManager) {
    log.info("Reloading manual spoof configurations...")
    let activeMACs = hostManager.getActiveMACs()

    return async(() => {
      await (emptySpoofSet()) // this is to ensure no other ip addresses are added to the list
      activeMACs.forEach((mac) => {
        await (this.loadManualSpoof(mac))
      })
    })()
  }

  isSpoofRunning() {
    return async(() => {
      try {
        await (exec("pgrep -x bitbridge7"))

        // TODO: add ipv6 check in the future
      } catch(err) {
        // error means no bitbridge7 is available
        log.warn("service bitbridge7 is not running (yet)")
        return false
      }
      return true
    })()
  }

  // TODO support ipv6
  isSpoof(ip) {
    return async(() => {

      try {
        await (exec("pgrep -x bitbridge7"))

        // TODO: add ipv6 check in the future
      } catch(err) {
        // error means no bitbridge7 is available
        log.warn("service bitbridge7 is not running (yet)")
        return false
      }

      // BitBridge.getInstance() is not available for other nodejs processes than FireMain
      /*
      let instance = BitBridge.getInstance()
      if(!instance) {
        return false
      }

      let started = instance.started
      if(!started) {
        return false
      }
      */

      return await (rclient.sismemberAsync(monitoredKey, ip)) == 1
    })()
  }
}
<commit_msg>enable ipv6 for non-prod branch by default<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict'

let firewalla = require('./Firewalla.js');

let BitBridge = require('../extension/bitbridge/bitbridge.js')

var spawn = require('child_process').spawn;

let spawnProcess = null;

let spoofLogFile = firewalla.getLogFolder() + "/spoof.log";

let SysManager = require("./SysManager.js");
let sysManager = new SysManager();

let Promise = require('bluebird');

const async = require('asyncawait/async')
const await = require('asyncawait/await')

const iptool = require('ip')

const rclient = require('../util/redis_manager.js').getRedisClient()
const sclient = require('../util/redis_manager.js').getSubscriptionClient()

let log = require("./logger.js")(__filename, 'info');

let fs = require('fs');

let cp = require('child_process');

let monitoredKey = "monitored_hosts";
let unmonitoredKey = "unmonitored_hosts";
const unmonitoredKeyAll = "unmonitored_hosts_all";
let monitoredKey6 = "monitored_hosts6";
let unmonitoredKey6 = "unmonitored_hosts6";
const sem = require('../sensor/SensorEventManager.js').getInstance();

let HostTool = require('../net2/HostTool')
let hostTool = new HostTool();
const fc = require('./config.js')

let exec = require('child-process-promise').exec

let instance = null;

module.exports = class SpooferManager {
  constructor() {
    if (!instance) {
      this.spoofStarted = false;
      this.registeredSpoofInstances = {};
      (async () => {
        const gatewayIp = sysManager.myGateway();
        this.gatewayMac = await hostTool.getMacByIP(gatewayIp);
      })();

      if (firewalla.isMain()) {
        sem.on("DeviceUpdate", (event) => {
          const host = event.host;
          if (sysManager.myGateway6() && this.gatewayMac && host.mac === this.gatewayMac
            && host.ipv6Addr && sysManager.myDNS().includes(sysManager.myGateway())) {
            // v4 dns includes gateway ip, very likely gateway's v6 addresses are dns servers, need to spoof these addresses (no matter public or linklocal)
            log.info("Router also acts as dns, spoof all router's v6 addresses: ", host.ipv6Addr);
            for (let i in host.ipv6Addr) {
              const addr = host.ipv6Addr[i];
              this.registerSpoofInstance(sysManager.monitoringInterface().name, addr, sysManager.myIp6(), true);
            }
          }
        });

        sclient.subscribe("System:IPChange")

        sclient.on("message", (channel, message) => {
          switch (channel) {
            case "System:IPChange":
              this.registerSpoofInstance(sysManager.monitoringInterface().name, sysManager.myGateway(), sysManager.myIp(), false);
              if (sysManager.myGateway6()) {
                this.registerSpoofInstance(sysManager.monitoringInterface().name, sysManager.myGateway6(), sysManager.myIp6()[0], true);
              }
              break;
            default:
          }
        });

        // feature change listener
        (async () => {
          let ipv6Default = false;
          if (firewalla.isBeta() || firewalla.isAlpha() || firewalla.isDevelopmentVersion()) {
            ipv6Default = true;
          }
          if(fc.isFeatureOn("ipv6", ipv6Default)) {
            await fc.enableDynamicFeature("ipv6"); // ensure dynamic feature flag is set
            await this.ipv6On();
          } else {
            await fc.disableDynamicFeature("ipv6"); // ensure dynamic feature flag is cleared
            await this.ipv6Off();
          }
          fc.onFeature("ipv6", (feature, status) => {
            if(feature != "ipv6")
              return

            if(status) {
              this.ipv6On()
            } else {
              this.ipv6Off()
            }
          })
        })()
      }
      instance = this;
    }
    return instance;
  }

  registerSpoofInstance(intf, routerIP, selfIP, isV6) {
    const key = this._getSpoofInstanceKey(intf, routerIP, selfIP, isV6);
    if (!key)
      return;

    if (!this.registeredSpoofInstances[key]) {
      this.registeredSpoofInstances[key] = BitBridge.createInstance(intf, routerIP, selfIP, isV6);
      if (this.spoofStarted) {
        this.registeredSpoofInstances[key].start();
      }
    } else {
      const oldInstance = this.registeredSpoofInstances[key];
      const newInstance = BitBridge.createInstance(intf, routerIP, selfIP, isV6);
      if (newInstance !== oldInstance) {
        // need to deregister old instance
        this.deregisterSpoofInstance(intf, routerIP, selfIP, isV6);
        this.registeredSpoofInstances[key] = newInstance;
        if (this.spoofStarted) {
          newInstance.start();
        }
      }
    }
  }

  deregisterSpoofInstance(intf, routerIP, selfIP, isV6) {
    const key = this._getSpoofInstanceKey(intf, routerIP, selfIP, isV6);
    if (!key)
      return;

    if (this.registeredSpoofInstances[key]) {
      const spoofInstance = this.registeredSpoofInstances[key];
      if (this.spoofStarted) {
        spoofInstance.stop();
      }
      delete this.registeredSpoofInstances[key];
    }
  }

  _getSpoofInstanceKey(intf, routerIP, selfIP, isV6) {
    isV6 = isV6 || false;
    if (!routerIP) {
      log.error("Cannot create bitbridge instance. Router IP should be specified.");
      return null;
    }
    if (!selfIP && !isV6) {
      log.error("Cannot create bitbridge instance. Self IP should be specified for ipv4.");
      return null;
    }
    intf = intf || "eth0";
    if (isV6) {
      return `${intf}_v6_${routerIP}`;
    } else {
      return `${intf}_v4_${routerIP}`;
    }
  }

  async ipv6On() {
    try {
      await exec("touch /home/pi/.firewalla/config/enablev6");
      await exec("pgrep -x bitbridge6 && sudo pkill bitbridge6; true");
    } catch(err) {
      log.warn("Error when turn on ipv6", err);
    }
  }

  async ipv6Off() {
    try {
      await exec("rm -f /home/pi/.firewalla/config/enablev6");
      await exec("pgrep -x bitbridge6 && sudo pkill bitbridge6; true");
    } catch(err) {
      log.warn("Error when turn off ipv6", err);
    }
  }

  // WORKAROUND VERSION HERE, will move to a better place
  async startSpoofing() {

    if(this.spoofStarted) {
      return;
    }

    log.info("start spoofing")

    await this.emptySpoofSet(); // all monitored_hosts* keys are cleared during startup

    await BitBridge.cleanupSpoofInstanceConfigs(); // cleanup Bitbridge config files (*.rc)

    for (let key in this.registeredSpoofInstances) {
      const spoofInstance = this.registeredSpoofInstances[key];
      spoofInstance.start();
    }

    /*
    let ifName = sysManager.monitoringInterface().name;
    let routerIP = sysManager.myGateway();
    let myIP = sysManager.myIp();
    let gateway6 = sysManager.myGateway6();

    if(!ifName || !myIP || !routerIP) {
      return Promise.reject("require valid interface name, ip address and gateway ip address");
    }

    let b7 = new BitBridge(ifName, routerIP, myIP,null,null,gateway6)
    b7.start()
    */

    this.spoofStarted = true;
  }

  async stopSpoofing() {
    try {
      this.spoofStarted = false;

      for (let key in this.registeredSpoofInstances) {
        const spoofInstance = this.registeredSpoofInstances[key];
        await spoofInstance.stop();
      }
      /*
      let ifName = sysManager.monitoringInterface().name;
      let routerIP = sysManager.myGateway();
      let myIP = sysManager.myIp();

      if(!ifName || !myIP || !routerIP) {
        return Promise.reject("require valid interface name, ip address and gateway ip address");
      }

      let b7 = new BitBridge(ifName, routerIP, myIP)
      b7.stop()
        .then(() => {
          resolve()
        })
      */
    } catch (err) {
      //catch everything here
      log.error("Failed to stop spoofing:", err);
    }
  }

  directSpoof(ip) {
    return async(() => {
      if(iptool.isV4Format(ip)) {
        await (rclient.saddAsync(monitoredKey, ip))
      } else if(iptool.isV6Format(ip)) {
        await (rclient.saddAsync(monitoredKey6, ip))
      } else {
        return Promise.reject(new Error("Invalid ip address: " + ip))
      }
    })()
  }

  emptySpoofSet() {
    return async(() => {
      // clean up redis key
      await (rclient.delAsync(monitoredKey))
      await (rclient.delAsync(unmonitoredKey))
      await (rclient.delAsync(unmonitoredKeyAll))
      await (rclient.delAsync(monitoredKey6))
      await (rclient.delAsync(unmonitoredKey6))
    })()
  }

  loadManualSpoof(mac) {
    return async(() => {
      let key = hostTool.getMacKey(mac)
      let host = await (rclient.hgetallAsync(key))
      let manualSpoof = (host.manualSpoof === '1' ? true : false)
      if(manualSpoof) {
        await (rclient.saddAsync(monitoredKey, host.ipv4Addr))
        await (rclient.sremAsync(unmonitoredKey, host.ipv4Addr))
        await (rclient.sremAsync(unmonitoredKeyAll, host.ipv4Addr))
      } else {
        await (rclient.sremAsync(monitoredKey, host.ipv4Addr))
        await (rclient.saddAsync(unmonitoredKey, host.ipv4Addr))
        await (rclient.saddAsync(unmonitoredKeyAll, host.ipv4Addr))
        setTimeout(() => {
          rclient.sremAsync(unmonitoredKey, host.ipv4Addr)
        }, 8 * 1000) // remove ip from unmonitoredKey after 8 seconds to reduce battery cost of unmonitored devices
      }
    })()
  }

  loadManualSpoofs(hostManager) {
    log.info("Reloading manual spoof configurations...")
    let activeMACs = hostManager.getActiveMACs()

    return async(() => {
      await (emptySpoofSet()) // this is to ensure no other ip addresses are added to the list
      activeMACs.forEach((mac) => {
        await (this.loadManualSpoof(mac))
      })
    })()
  }

  isSpoofRunning() {
    return async(() => {
      try {
        await (exec("pgrep -x bitbridge7"))

        // TODO: add ipv6 check in the future
      } catch(err) {
        // error means no bitbridge7 is available
        log.warn("service bitbridge7 is not running (yet)")
        return false
      }
      return true
    })()
  }

  // TODO support ipv6
  isSpoof(ip) {
    return async(() => {

      try {
        await (exec("pgrep -x bitbridge7"))

        // TODO: add ipv6 check in the future
      } catch(err) {
        // error means no bitbridge7 is available
        log.warn("service bitbridge7 is not running (yet)")
        return false
      }

      // BitBridge.getInstance() is not available for other nodejs processes than FireMain
      /*
      let instance = BitBridge.getInstance()
      if(!instance) {
        return false
      }

      let started = instance.started
      if(!started) {
        return false
      }
      */

      return await (rclient.sismemberAsync(monitoredKey, ip)) == 1
    })()
  }
}
<|endoftext|>


<commit_before>'use strict';

require('chai').should();

var encode = require('../lib/encode');

describe('encode()', function () {

  it('should encode URLs', function () {
    var encoded = encode('https://www.uribeacon.org/test');

    encoded[0].should.equal(0x01); // 'https://www.' prefix

    encoded[1].should.equal('u'.charCodeAt(0));
    encoded[2].should.equal('r'.charCodeAt(0));
    encoded[3].should.equal('i'.charCodeAt(0));

    encoded[10].should.equal(0x01); // '.org/' suffix

    encoded[11].should.equal('t'.charCodeAt(0));
    encoded[12].should.equal('e'.charCodeAt(0));
    encoded[13].should.equal('s'.charCodeAt(0));
    encoded[14].should.equal('t'.charCodeAt(0));

    encoded.length.should.equal(15);
  });

  it('should encode URLs and strip trailing slashes', function () {
    encode('https://www.uribeacon.org/')[10].should.equal(0x08); // '.org' suffix
    encode('http://1.at/').length.should.equal(1 + 4); // 1 prefix + 4 characters
  });

  it('should throw a TypeError if something other than a string is provided', function () {
    [123, {}, false].forEach(function (value) {
      encode.bind(encode, value).should.throw(TypeError);
    });
  });

  it('should throw an Error if the encoded URL is longer than 18 bytes', function () {
    encode.bind(encode, 'http://this.is.clearly.longer.than.18.bytes').should.throw(Error, 'Encoded URL (http://this.is.clearly.longer.than.18.bytes) is too long (max 18 bytes): 37 bytes');
  });

  it('should throw an Error if non-http/https URLs are being encoded', function () {
    encode.bind(encode, 'urn:uuid:b1e13d51-5fc9-4d5b-902b-ab668dd54981').should.throw(Error, 'Only "http://" and "https://" URLs can be encoded');
  });

});
<commit_msg>Fix unit tests<commit_after>'use strict';

require('chai').should();

var encode = require('../lib/encode');

describe('encode()', function () {

  it('should encode URLs', function () {
    var encoded = encode('https://www.uribeacon.org/test');

    encoded[0].should.equal(0x01); // 'https://www.' prefix

    encoded[1].should.equal('u'.charCodeAt(0));
    encoded[2].should.equal('r'.charCodeAt(0));
    encoded[3].should.equal('i'.charCodeAt(0));

    encoded[10].should.equal(0x01); // '.org/' suffix

    encoded[11].should.equal('t'.charCodeAt(0));
    encoded[12].should.equal('e'.charCodeAt(0));
    encoded[13].should.equal('s'.charCodeAt(0));
    encoded[14].should.equal('t'.charCodeAt(0));

    encoded.length.should.equal(15);
  });

  it('should encode URLs', function () {
    encode('https://www.uribeacon.org/')[10].should.equal(0x01); // '.org/' suffix

    encode('http://1.com').length.should.equal(1 + 2); // 1 prefix + 2 characters
    encode('http://1.com/').length.should.equal(1 + 2); // 1 prefix + 2 characters

    encode('http://1.at/').length.should.equal(1 + 5); // 1 prefix + 5 characters
  });

  it('should throw a TypeError if something other than a string is provided', function () {
    [123, {}, false].forEach(function (value) {
      encode.bind(encode, value).should.throw(TypeError);
    });
  });

  it('should throw an Error if the encoded URL is longer than 18 bytes', function () {
    encode.bind(encode, 'http://this.is.clearly.longer.than.18.bytes').should.throw(Error, 'Encoded URL (http://this.is.clearly.longer.than.18.bytes) is too long (max 18 bytes): 37 bytes');
  });

  it('should throw an Error if non-http/https URLs are being encoded', function () {
    encode.bind(encode, 'urn:uuid:b1e13d51-5fc9-4d5b-902b-ab668dd54981').should.throw(Error, 'Only "http://" and "https://" URLs can be encoded');
  });

});
<|endoftext|>