<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/*
 * Copyright (c) 2017, Joyent, Inc.
 */

/*
 * Test the Package API endpoints,
 */


var fs      = require('fs');
var path    = require('path');
var qs      = require('querystring');
var restify = require('restify');
var test    = require('tape').test;
var util    = require('util');
var Logger  = require('bunyan');
var libuuid = require('libuuid');

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



// If we're in the papi zone, use the papi config. Otherwise assume we're on
// someone's laptop and they're using the default COAL IP addresses.
var cfgFile = path.resolve(__dirname, '../etc/config.json');
cfgFile = fs.existsSync(cfgFile) ? cfgFile :
               path.resolve(__dirname, '../etc/config.test.json');

var config = JSON.parse(fs.readFileSync(cfgFile, 'utf-8'));

// For tests, override the port to 8080 so we don't block (or get queries for)
// real papi if we're running in the zone.
config.port = 8080;

var packages = [ {
    v: 1,
    uuid: '27543bf3-0c66-4f61-9ae4-7dda5cb4741b',
    name: 'api_test_128',
    version: '1.0.0',
    os: 'smartos',
    max_physical_memory: 128,
    quota: 5120,
    max_swap: 256,
    cpu_cap: 350,
    cpu_burst_ratio: 0.5,
    max_lwps: 2000,
    zfs_io_priority: 1,
    default: true,
    vcpus: 1,
    active: true,
    networks: [
        'aefd7d3c-a4fd-4812-9dd7-24733974d861',
        'de749393-836c-42ce-9c7b-e81072ca3a23'
    ],
    traits: {
        bool: true,
        arr: ['one', 'two', 'three'],
        str: 'a string'
    },
    group: 'ramones',
    description: 'This is a package description, and should be present',
    common_name: 'API Test 128MiB',
    fss: 25,
    billing_tag: 'ApiTest128MiB',
    alloc_server_spread: 'random'
}, {
    v: 1,
    uuid: '43cedda8-f844-4a62-956a-85691fa21b36',
    name: 'api_test_2048',
    version: '1.0.1',
    active: false,
    cpu_cap: 300,
    default: true,
    max_lwps: 2000,
    max_physical_memory: 2048,
    max_swap: 4096,
    quota: 81920,
    zfs_io_priority: 50,
    owner_uuids: ['7f5501af-12da-4727-8579-625e527ed1f2'],
    alloc_server_spread: 'min-owner'
}, {
    v: 1,
    uuid: '9cfe7e8b-d1c8-40a5-8e20-214d43f95124',
    name: 'api_test_512',
    version: '1.0.1',
    active: true,
    cpu_cap: 300,
    default: false,
    max_lwps: 1000,
    max_physical_memory: 512,
    max_swap: 1024,
    quota: 10240,
    zfs_io_priority: 100,
    owner_uuids: [
        '7f5501af-12da-4727-8579-625e527ed1f2',
        'c39b6d6a-1c11-4d1b-b213-174974d71b45'
    ]
} ];



var server, client, backend, startTime;



test('setup', function (t) {
    var log = new Logger({
        level: process.env.LOG_LEVEL || 'info',
        name: 'sdc-package-api-test',
        stream: process.stdout,
        serializers: restify.bunyan.serializers
    });

    papi.createServer({
        config: cfgFile,
        log: log,
        overrides: config,
        test: true
    }, function (err, s) {
        server  = s;
        backend = server.backend;

        t.ok(server);
        t.ok(backend);

        client = restify.createJsonClient({
            log: s.log,
            url: 'http://127.0.0.1:' + config.port,
            version: '*',
            retryOptions: {
                retry: 0
            }
        });

        t.ok(client);

        startTime = +new Date();

        t.end();
    });
});



test('Clean up stale state (before)', cleanUp);
test('Check no stale packages (before)', checkNoPkgs);



test('GET /ping', function (t) {
    client.get('/ping', function (err, req, res, health) {
        t.ifError(err);
        t.equal(res.statusCode, 200);
        t.ok(typeof (health) === 'object');
        t.ok(typeof (health.pid) === 'number');
        t.equal(health.backend, 'up');
        t.end();
    });
});



test('GET /packages', function (t) {
    client.get('/packages', function (err, req, res, pkgs) {
        t.ifError(err);
        t.equal(res.statusCode, 200);
        t.ok(res.headers['x-resource-count']);
        t.ok(Array.isArray(pkgs));
        t.end();
    });
});



test('POST /packages (OK)', function (t) {
    var postPkgs = packages.slice();

    var postPkg = function () {
        var newPkg = postPkgs.pop();

        client.post('/packages', newPkg, function (err, req, res, storedPkg) {
            t.ifError(err);
            t.equal(res.statusCode, 201);

            checkDate(t, storedPkg);
            t.deepEqual(newPkg, storedPkg);

            var location = res.headers['location'];
            t.equal(location, '/packages/' + newPkg.uuid);

            client.get(location, function (err2, req2, res2, storedPkg2) {
                t.ifError(err2);
                t.equal(res2.statusCode, 200);

                checkDate(t, storedPkg2);
                t.deepEqual(newPkg, storedPkg2);

                if (postPkgs.length > 0)
                    return postPkg();

                return t.end();
            });
        });
    };

    postPkg();
});



test('POST /packages/:uuid (bad fields)', function (t) {
    var badPkg = {
        active: false,
        cpu_cap: 100,
        max_lwps: 1000,
        max_physical_memory: 1024,
        max_swap: 2048,
        name: 'foo',
        quota: 10240,
        version: '1.0.0',
        zfs_io_priority: 100,
        foobarbaz: true  // unrecognised
    };

    client.post('/packages', badPkg, function (err, req, res, pkg) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Unrecognised attributes');

        var expectedErrs = [
            { field: 'foobarbaz',
              code: 'Invalid',
              message: 'is an unsupported attribute' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages/:uuid (invalid package name)', function (t) {
    var badPkg = {
        active: false,
        cpu_cap: 100,
        default: false,
        max_lwps: 1000,
        max_physical_memory: 1024,
        max_swap: 2048,
        name: '@#$\'!',
        quota: 10240,
        version: '1.0.0',
        zfs_io_priority: 100
    };

    client.post('/packages', badPkg, function (err, req, res, pkg) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'name',
              code: 'Invalid',
              message: 'must match '
                  + '/^[a-zA-Z0-9]([a-zA-Z0-9\\_\\-\\.]+)?[a-zA-Z0-9]$/ and not'
                  + ' contain repeated \'-\', \'_\' or \'.\' characters'
            }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages (missing required fields)', function (t) {
    var pkg = {
        vcpus: 1,
        networks: [
            'aefd7d3c-a4fd-4812-9dd7-24733974d861',
            'de749393-836c-42ce-9c7b-e81072ca3a23'
        ],
        traits: {
            bool: true,
            arr: ['one', 'two', 'three'],
            str: 'a string'
        }
    };

    client.post('/packages', pkg, function (err, req, res, _) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'active', code: 'Missing', message: 'is missing' },
            { field: 'cpu_cap', code: 'Missing', message: 'is missing' },
            { field: 'max_lwps', code: 'Missing', message: 'is missing' },
            { field: 'max_physical_memory',
              code: 'Missing',
              message: 'is missing' },
            { field: 'max_swap', code: 'Missing', message: 'is missing' },
            { field: 'name', code: 'Missing', message: 'is missing' },
            { field: 'quota', code: 'Missing', message: 'is missing' },
            { field: 'version', code: 'Missing', message: 'is missing' },
            { field: 'zfs_io_priority',
              code: 'Missing',
              message: 'is missing' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages (empty required fields)', function (t) {
    var pkg = {
        uuid: '', // uuid gets set if empty or null
        name: '',
        version: '',
        active: true,
        cpu_cap: 300,
        default: false,
        max_lwps: 1000,
        max_physical_memory: 128,
        max_swap: 128,
        quota: 2048,
        zfs_io_priority: 10
    };

    client.post('/packages', pkg, function (err, req, res, _) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'name', code: 'Missing', message: 'is empty' },
            { field: 'version', code: 'Missing', message: 'is empty' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages (fields validation failed)', function (t) {
    var pkg = {
        name: 'regular_128',
        version: '1.0.0',
        os: 2,
        max_physical_memory: 32,
        quota: 512,
        max_swap: 256,
        cpu_cap: 350,
        max_lwps: 2000,
        zfs_io_priority: 100000,
        'default': true,
        vcpus: 30,
        active: true,
        networks: [
            'aefd7d3c-a4fd-4812-9dd7-24733974d861',
            'de749393-836c-42ce-9c7b-'
        ],
        traits: {
            bool: true,
            arr: ['one', 'two', 'three'],
            str: 'a string'
        },
        group: 'ramones',
        uuid: 'invalid-uuid-for-sure',
        description: 'This is a package description, and should be present',
        common_name: 'Regular 128MiB',
        fss: 25,
        alloc_server_spread: 'invalid'
    };

    client.post('/packages', pkg, function (err, req, res, _) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'networks',
              code: 'Invalid',
              message: 'must only contain UUIDs' },
            { field: 'os', code: 'Invalid', message: 'must be string' },
            { field: 'uuid', code: 'Invalid', message: 'must be UUID' },
            { field: 'alloc_server_spread',
              code: 'Invalid',
              message: 'must be one of: min-ram, random, min-owner' },
            { field: 'max_physical_memory',
              code: 'Invalid',
              message: 'must be greater or equal to 64' },
            { field: 'quota',
              code: 'Invalid',
              message: 'must be greater or equal to 1024' },
            { field: 'quota',
              code: 'Invalid',
              message: 'must be a multiple of 1024' },
            { field: 'zfs_io_priority',
              code: 'Invalid',
              message: 'must be greater or equal to 0, and less than or ' +
                  'equal to 16383' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages (quota must be multiple of 1024)', function (t) {
    var pkg = {
        name: 'regular_128',
        version: '1.0.0',
        os: 'smartos',
        max_physical_memory: 64,
        quota: 1280,
        max_swap: 256,
        cpu_cap: 350,
        max_lwps: 2000,
        zfs_io_priority: 100,
        'default': true,
        vcpus: 30,
        active: true,
        group: 'ramones',
        uuid: 'ebb58a8c-b77e-4559-bbf0-19ebd67973f0',
        description: 'This is a package description, and should be present',
        common_name: 'Regular 128MiB',
        fss: 25
    };

    client.post('/packages', pkg, function (err, req, res, _) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'quota',
              code: 'Invalid',
              message: 'must be a multiple of 1024' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});


test('POST /packages (duplicated unique field)', function (t) {
    client.post('/packages', packages[0], function (err, req, res, pkg) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        var expected = {
            code: 'ConflictError',
            message: 'A package with the given UUID already exists'
        };

        t.deepEqual(err.body, expected);
        t.end();
    });
});



test('GET /packages/:uuid (OK)', checkPkg1);



test('GET /packages/:uuid (404)', function (t) {
    var badUuid = uuid();

    client.get('/packages/' + badUuid, function (err, req, res, pkg) {
        t.equal(res.statusCode, 404);

        var expected = {
            code: 'ResourceNotFound',
            message: 'Package ' + badUuid + ' does not exist'
        };

        t.deepEqual(err.body, expected);
        t.end();
    });
});



test('GET /packages (Search by owner_uuids)', function (t) {
    var query = '/packages?owner_uuids=' + config.ufds_admin_uuid;

    client.get(query, function (err, req, res, pkgs) {
        t.ifError(err);
        t.equal(res.statusCode, 200);
        t.ok(res.headers['x-resource-count']);
        t.ok(Array.isArray(pkgs));
        t.ok(pkgs.length > 0);

        pkgs.forEach(function (p) {
            t.ok(p.owner_uuids === undefined ||
                 p.owner_uuids.indexOf(config.ufds_admin_uuid) !== -1);
        });

        t.end();
    });
});



test('GET /packages (Search by group)', function (t) {
    var query = '/packages?group=ramones';

    var testFilter = function (p) {
        return p.group === 'ramones';
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search by networks)', function (t) {
    var network = packages[0].networks[1];
    var query = '/packages?networks=' + network;

    var testFilter = function (p) {
        if (!p.networks)
            return false;

        return p.networks.indexOf(network) !== -1;
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search by name)', function (t) {
    var query = '/packages?name=api_test_128';

    var testFilter = function (p) {
        return p.name === 'api_test_128';
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search by wildcard)', function (t) {
    var query = '/packages?name=api_test_*';

    var testFilter = function (p) {
        return /^api_test_/.test(p.name);
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search by multiple fields)', function (t) {
    var query = '/packages?name=api_test_*&owner_uuids=' + uuid();

    var testFilter = function (p) {
        return /^api_test_/.test(p.name) && !p.owner_uuids;
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search with LDIF injection attempt)', function (t) {
    client.get({
        path: '/packages',
        query: {
            name: 'api_test_*',
            networks: '*)(owner_uuids={\\2a}'
        }
    }, function (err, req, res, body) {
        t.equal(res.statusCode, 500);

        t.deepEqual(body, {
            code: 'InternalError',
            message: 'sdc_packages does not have indexes that support ' +
                     '(networks=*{\\)}{\\(}owner_uuids{\\=}{\\*})'
        });
<commit_msg>PAPI-136: Add a test to cover multiple wildcards Reviewed by: <NAME> <<EMAIL>><commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/*
 * Copyright (c) 2017, Joyent, Inc.
 */

/*
 * Test the Package API endpoints,
 */


var fs      = require('fs');
var path    = require('path');
var qs      = require('querystring');
var restify = require('restify');
var test    = require('tape').test;
var util    = require('util');
var Logger  = require('bunyan');
var libuuid = require('libuuid');

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



// If we're in the papi zone, use the papi config. Otherwise assume we're on
// someone's laptop and they're using the default COAL IP addresses.
var cfgFile = path.resolve(__dirname, '../etc/config.json');
cfgFile = fs.existsSync(cfgFile) ? cfgFile :
               path.resolve(__dirname, '../etc/config.test.json');

var config = JSON.parse(fs.readFileSync(cfgFile, 'utf-8'));

// For tests, override the port to 8080 so we don't block (or get queries for)
// real papi if we're running in the zone.
config.port = 8080;

var packages = [ {
    v: 1,
    uuid: '27543bf3-0c66-4f61-9ae4-7dda5cb4741b',
    name: 'api_test_128',
    version: '1.0.0',
    os: 'smartos',
    max_physical_memory: 128,
    quota: 5120,
    max_swap: 256,
    cpu_cap: 350,
    cpu_burst_ratio: 0.5,
    max_lwps: 2000,
    zfs_io_priority: 1,
    default: true,
    vcpus: 1,
    active: true,
    networks: [
        'aefd7d3c-a4fd-4812-9dd7-24733974d861',
        'de749393-836c-42ce-9c7b-e81072ca3a23'
    ],
    traits: {
        bool: true,
        arr: ['one', 'two', 'three'],
        str: 'a string'
    },
    group: 'ramones',
    description: 'This is a package description, and should be present',
    common_name: 'API Test 128MiB',
    fss: 25,
    billing_tag: 'ApiTest128MiB',
    alloc_server_spread: 'random'
}, {
    v: 1,
    uuid: '43cedda8-f844-4a62-956a-85691fa21b36',
    name: 'api_test_2048',
    version: '1.0.1',
    active: false,
    cpu_cap: 300,
    default: true,
    max_lwps: 2000,
    max_physical_memory: 2048,
    max_swap: 4096,
    quota: 81920,
    zfs_io_priority: 50,
    owner_uuids: ['7f5501af-12da-4727-8579-625e527ed1f2'],
    alloc_server_spread: 'min-owner'
}, {
    v: 1,
    uuid: '9cfe7e8b-d1c8-40a5-8e20-214d43f95124',
    name: 'api_test_512',
    version: '1.0.1',
    active: true,
    cpu_cap: 300,
    default: false,
    max_lwps: 1000,
    max_physical_memory: 512,
    max_swap: 1024,
    quota: 10240,
    zfs_io_priority: 100,
    owner_uuids: [
        '7f5501af-12da-4727-8579-625e527ed1f2',
        'c39b6d6a-1c11-4d1b-b213-174974d71b45'
    ]
} ];



var server, client, backend, startTime;



test('setup', function (t) {
    var log = new Logger({
        level: process.env.LOG_LEVEL || 'info',
        name: 'sdc-package-api-test',
        stream: process.stdout,
        serializers: restify.bunyan.serializers
    });

    papi.createServer({
        config: cfgFile,
        log: log,
        overrides: config,
        test: true
    }, function (err, s) {
        server  = s;
        backend = server.backend;

        t.ok(server);
        t.ok(backend);

        client = restify.createJsonClient({
            log: s.log,
            url: 'http://127.0.0.1:' + config.port,
            version: '*',
            retryOptions: {
                retry: 0
            }
        });

        t.ok(client);

        startTime = +new Date();

        t.end();
    });
});



test('Clean up stale state (before)', cleanUp);
test('Check no stale packages (before)', checkNoPkgs);



test('GET /ping', function (t) {
    client.get('/ping', function (err, req, res, health) {
        t.ifError(err);
        t.equal(res.statusCode, 200);
        t.ok(typeof (health) === 'object');
        t.ok(typeof (health.pid) === 'number');
        t.equal(health.backend, 'up');
        t.end();
    });
});



test('GET /packages', function (t) {
    client.get('/packages', function (err, req, res, pkgs) {
        t.ifError(err);
        t.equal(res.statusCode, 200);
        t.ok(res.headers['x-resource-count']);
        t.ok(Array.isArray(pkgs));
        t.end();
    });
});



test('POST /packages (OK)', function (t) {
    var postPkgs = packages.slice();

    var postPkg = function () {
        var newPkg = postPkgs.pop();

        client.post('/packages', newPkg, function (err, req, res, storedPkg) {
            t.ifError(err);
            t.equal(res.statusCode, 201);

            checkDate(t, storedPkg);
            t.deepEqual(newPkg, storedPkg);

            var location = res.headers['location'];
            t.equal(location, '/packages/' + newPkg.uuid);

            client.get(location, function (err2, req2, res2, storedPkg2) {
                t.ifError(err2);
                t.equal(res2.statusCode, 200);

                checkDate(t, storedPkg2);
                t.deepEqual(newPkg, storedPkg2);

                if (postPkgs.length > 0)
                    return postPkg();

                return t.end();
            });
        });
    };

    postPkg();
});



test('POST /packages/:uuid (bad fields)', function (t) {
    var badPkg = {
        active: false,
        cpu_cap: 100,
        max_lwps: 1000,
        max_physical_memory: 1024,
        max_swap: 2048,
        name: 'foo',
        quota: 10240,
        version: '1.0.0',
        zfs_io_priority: 100,
        foobarbaz: true  // unrecognised
    };

    client.post('/packages', badPkg, function (err, req, res, pkg) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Unrecognised attributes');

        var expectedErrs = [
            { field: 'foobarbaz',
              code: 'Invalid',
              message: 'is an unsupported attribute' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages/:uuid (invalid package name)', function (t) {
    var badPkg = {
        active: false,
        cpu_cap: 100,
        default: false,
        max_lwps: 1000,
        max_physical_memory: 1024,
        max_swap: 2048,
        name: '@#$\'!',
        quota: 10240,
        version: '1.0.0',
        zfs_io_priority: 100
    };

    client.post('/packages', badPkg, function (err, req, res, pkg) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'name',
              code: 'Invalid',
              message: 'must match '
                  + '/^[a-zA-Z0-9]([a-zA-Z0-9\\_\\-\\.]+)?[a-zA-Z0-9]$/ and not'
                  + ' contain repeated \'-\', \'_\' or \'.\' characters'
            }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages (missing required fields)', function (t) {
    var pkg = {
        vcpus: 1,
        networks: [
            'aefd7d3c-a4fd-4812-9dd7-24733974d861',
            'de749393-836c-42ce-9c7b-e81072ca3a23'
        ],
        traits: {
            bool: true,
            arr: ['one', 'two', 'three'],
            str: 'a string'
        }
    };

    client.post('/packages', pkg, function (err, req, res, _) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'active', code: 'Missing', message: 'is missing' },
            { field: 'cpu_cap', code: 'Missing', message: 'is missing' },
            { field: 'max_lwps', code: 'Missing', message: 'is missing' },
            { field: 'max_physical_memory',
              code: 'Missing',
              message: 'is missing' },
            { field: 'max_swap', code: 'Missing', message: 'is missing' },
            { field: 'name', code: 'Missing', message: 'is missing' },
            { field: 'quota', code: 'Missing', message: 'is missing' },
            { field: 'version', code: 'Missing', message: 'is missing' },
            { field: 'zfs_io_priority',
              code: 'Missing',
              message: 'is missing' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages (empty required fields)', function (t) {
    var pkg = {
        uuid: '', // uuid gets set if empty or null
        name: '',
        version: '',
        active: true,
        cpu_cap: 300,
        default: false,
        max_lwps: 1000,
        max_physical_memory: 128,
        max_swap: 128,
        quota: 2048,
        zfs_io_priority: 10
    };

    client.post('/packages', pkg, function (err, req, res, _) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'name', code: 'Missing', message: 'is empty' },
            { field: 'version', code: 'Missing', message: 'is empty' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages (fields validation failed)', function (t) {
    var pkg = {
        name: 'regular_128',
        version: '1.0.0',
        os: 2,
        max_physical_memory: 32,
        quota: 512,
        max_swap: 256,
        cpu_cap: 350,
        max_lwps: 2000,
        zfs_io_priority: 100000,
        'default': true,
        vcpus: 30,
        active: true,
        networks: [
            'aefd7d3c-a4fd-4812-9dd7-24733974d861',
            'de749393-836c-42ce-9c7b-'
        ],
        traits: {
            bool: true,
            arr: ['one', 'two', 'three'],
            str: 'a string'
        },
        group: 'ramones',
        uuid: 'invalid-uuid-for-sure',
        description: 'This is a package description, and should be present',
        common_name: 'Regular 128MiB',
        fss: 25,
        alloc_server_spread: 'invalid'
    };

    client.post('/packages', pkg, function (err, req, res, _) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'networks',
              code: 'Invalid',
              message: 'must only contain UUIDs' },
            { field: 'os', code: 'Invalid', message: 'must be string' },
            { field: 'uuid', code: 'Invalid', message: 'must be UUID' },
            { field: 'alloc_server_spread',
              code: 'Invalid',
              message: 'must be one of: min-ram, random, min-owner' },
            { field: 'max_physical_memory',
              code: 'Invalid',
              message: 'must be greater or equal to 64' },
            { field: 'quota',
              code: 'Invalid',
              message: 'must be greater or equal to 1024' },
            { field: 'quota',
              code: 'Invalid',
              message: 'must be a multiple of 1024' },
            { field: 'zfs_io_priority',
              code: 'Invalid',
              message: 'must be greater or equal to 0, and less than or ' +
                  'equal to 16383' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});



test('POST /packages (quota must be multiple of 1024)', function (t) {
    var pkg = {
        name: 'regular_128',
        version: '1.0.0',
        os: 'smartos',
        max_physical_memory: 64,
        quota: 1280,
        max_swap: 256,
        cpu_cap: 350,
        max_lwps: 2000,
        zfs_io_priority: 100,
        'default': true,
        vcpus: 30,
        active: true,
        group: 'ramones',
        uuid: 'ebb58a8c-b77e-4559-bbf0-19ebd67973f0',
        description: 'This is a package description, and should be present',
        common_name: 'Regular 128MiB',
        fss: 25
    };

    client.post('/packages', pkg, function (err, req, res, _) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        t.equal(err.body.code, 'InvalidArgument');
        t.equal(err.body.message, 'Package is invalid');

        var expectedErrs = [
            { field: 'quota',
              code: 'Invalid',
              message: 'must be a multiple of 1024' }
        ];
        t.deepEqual(err.body.errors, expectedErrs);

        t.end();
    });
});


test('POST /packages (duplicated unique field)', function (t) {
    client.post('/packages', packages[0], function (err, req, res, pkg) {
        t.ok(err);
        t.equal(res.statusCode, 409);

        var expected = {
            code: 'ConflictError',
            message: 'A package with the given UUID already exists'
        };

        t.deepEqual(err.body, expected);
        t.end();
    });
});



test('GET /packages/:uuid (OK)', checkPkg1);



test('GET /packages/:uuid (404)', function (t) {
    var badUuid = uuid();

    client.get('/packages/' + badUuid, function (err, req, res, pkg) {
        t.equal(res.statusCode, 404);

        var expected = {
            code: 'ResourceNotFound',
            message: 'Package ' + badUuid + ' does not exist'
        };

        t.deepEqual(err.body, expected);
        t.end();
    });
});



test('GET /packages (Search by owner_uuids)', function (t) {
    var query = '/packages?owner_uuids=' + config.ufds_admin_uuid;

    client.get(query, function (err, req, res, pkgs) {
        t.ifError(err);
        t.equal(res.statusCode, 200);
        t.ok(res.headers['x-resource-count']);
        t.ok(Array.isArray(pkgs));
        t.ok(pkgs.length > 0);

        pkgs.forEach(function (p) {
            t.ok(p.owner_uuids === undefined ||
                 p.owner_uuids.indexOf(config.ufds_admin_uuid) !== -1);
        });

        t.end();
    });
});



test('GET /packages (Search by group)', function (t) {
    var query = '/packages?group=ramones';

    var testFilter = function (p) {
        return p.group === 'ramones';
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search by networks)', function (t) {
    var network = packages[0].networks[1];
    var query = '/packages?networks=' + network;

    var testFilter = function (p) {
        if (!p.networks)
            return false;

        return p.networks.indexOf(network) !== -1;
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search by name)', function (t) {
    var query = '/packages?name=api_test_128';

    var testFilter = function (p) {
        return p.name === 'api_test_128';
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search by wildcard)', function (t) {
    var query = '/packages?name=api_test_*';

    var testFilter = function (p) {
        return /^api_test_/.test(p.name);
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search by multiple wildcard)', function (t) {
    var query = '/packages?name=*pi_test_*';

    var testFilter = function (p) {
        return /^api_test_/.test(p.name);
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search by multiple fields)', function (t) {
    var query = '/packages?name=api_test_*&owner_uuids=' + uuid();

    var testFilter = function (p) {
        return /^api_test_/.test(p.name) && !p.owner_uuids;
    };

    searchAndCheckPkgs(t, query, testFilter);
});



test('GET /packages (Search with LDIF injection attempt)', function (t) {
    client.get({
        path: '/packages',
        query: {
            name: 'api_test_*',
            networks: '*)(owner_uuids={\\2a}'
        }
    }, function (err, req, res, body) {
        t.equal(res.statusCode, 500);

        t.deepEqual(body, {
            code: 'InternalError',
            message: 'sdc_packages does not have indexes that support ' +
                     '(networks=*{\\)}{\\(}owner_uuids{\\=}{\\*})'
        });

{'fraction_non_alphanumeric': 0.15300546448087432, 'fraction_numerical': 0.03875097580015613, 'mean_word_length': 1.9847157502329915, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3465204', 'n_tokens_mistral': 11605, 'n_tokens_neox': 10474, 'n_words': 2471}