"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const internal_1 = require("@mcph/miix-std/dist/internal");
const evilsniffer_1 = require("../src/server/metadata/evilsniffer");
const extractor_1 = require("../src/server/metadata/extractor");
const util_1 = require("../src/server/util");
describe('metadata', () => {
    describe('extractor', () => {
        const expectType = (expected, str) => {
            chai_1.expect(new extractor_1.MetadataExtractor().parseString(str).gatherResult()).to.containSubset({
                controls: {
                    button: {
                        inputs: [expected],
                    },
                },
            });
        };
        it('extracts a scene', () => {
            chai_1.expect(new extractor_1.MetadataExtractor()
                .parseString(`
            @Scene({ id: 'foo', default: true })
            class MyScene {}
          `)
                .gatherResult()).to.containSubset({
                scenes: {
                    foo: { id: 'foo', default: true },
                },
            });
        });
        it('extracts a control', () => {
            chai_1.expect(new extractor_1.MetadataExtractor()
                .parseString(`
            @Control({ kind: 'button' })
            class MyScene {}
          `)
                .gatherResult()).to.containSubset({
                controls: {
                    button: { kind: 'button' },
                },
            });
        });
        it('allows for namespaced decorators', () => {
            chai_1.expect(new extractor_1.MetadataExtractor()
                .parseString(`
            @Mixer.Control({ kind: 'button' })
            class MyScene {}
          `)
                .gatherResult()).to.containSubset({
                controls: {
                    button: { kind: 'button' },
                },
            });
        });
        it('parses data kinds', () => {
            chai_1.expect(new extractor_1.MetadataExtractor()
                .parseString(`
            @Mixer.Control({
              kind: 'button',
              level: 42,
              'quoted': [{ deeplyNested: true, isLame: false }],
              1: \`some numeric prop\`,
            })
            class MyScene {}
          `)
                .gatherResult()).to.containSubset({
                controls: {
                    button: {
                        kind: 'button',
                        level: 42,
                        quoted: [{ deeplyNested: true, isLame: false }],
                        1: 'some numeric prop',
                    },
                },
            });
        });
        it('infers the basic types from inputs', () => {
            expectType({ kind: internal_1.InputKind.Number, alias: 'foo', propertyName: 'foo' }, `@Control({ kind: 'button' })
        class MyScene {
          @Input()
          public foo: number;
        }`);
            expectType({ kind: internal_1.InputKind.String, alias: 'foo', propertyName: 'foo' }, `@Control({ kind: 'button' })
        class MyScene {
          @Input()
          public foo: string;
        }`);
            expectType({ kind: internal_1.InputKind.Boolean, alias: 'foo', propertyName: 'foo' }, `@Control({ kind: 'button' })
        class MyScene {
          @Input()
          public foo: boolean;
        }`);
            expectType({ kind: internal_1.InputKind.Dimensions, alias: 'foo', propertyName: 'foo' }, `@Control({ kind: 'button' })
        class MyScene {
          @Input()
          public foo: IDimensions;
        }`);
            expectType({ kind: internal_1.InputKind.Dimensions, alias: 'foo', propertyName: 'foo' }, `@Control({ kind: 'button' })
        class MyScene {
          @Input()
          public foo: Mixer.IDimensions;
        }`);
        });
        it('explcitly defines input types', () => {
            expectType({ kind: internal_1.InputKind.Color, alias: 'foo', propertyName: 'foo' }, `@Control({ kind: 'button' })
        class MyScene {
          @Input({ kind: InputKind.Color })
          public foo: number;
        }`);
            expectType({ kind: internal_1.InputKind.Color, alias: 'foo', propertyName: 'foo' }, `@Control({ kind: 'button' })
        class MyScene {
          @Input({ kind: Mixer.InputKind.Color })
          public foo: number;
        }`);
            expectType({ kind: internal_1.InputKind.Color, alias: 'foo', propertyName: 'foo' }, `@Control({ kind: 'button' })
        class MyScene {
          @Input({ kind: 'Color' })
          public foo: number;
        }`);
        });
        it('parses default values', () => {
            const str = `
        @Control({ kind: 'button' })
        class MyScene {
          @Input({ kind: InputKind.Color })
          public foo: string = '#fff';
        }
      `;
            chai_1.expect(new extractor_1.MetadataExtractor().parseString(str).gatherResult()).to.containSubset({
                controls: {
                    button: {
                        inputs: [
                            {
                                alias: 'foo',
                                propertyName: 'foo',
                                kind: internal_1.InputKind.Color,
                                defaultValue: '#fff',
                            },
                        ],
                    },
                },
            });
        });
        describe('invalid cases', () => {
            it('errors on shorthand props', () => {
                chai_1.expect(() => {
                    new extractor_1.MetadataExtractor().parseString(`
            @Control({ kind })
            class MyScene {}
          `);
                }).to.throw(/Only "key": "value" assignments are allowed in @Control/);
            });
            it('errors on wrong input enum', () => {
                chai_1.expect(() => {
                    new extractor_1.MetadataExtractor().parseString(`
            @Control({ kind: 'button' })
            class MyScene {
              @Input({ kind: InputWut.Color })
              public foo: number;
            }
          `);
                }).to.throw(/Expected to be a lookup on InputKind/);
            });
            it('errors on bad input enum property', () => {
                chai_1.expect(() => {
                    new extractor_1.MetadataExtractor().parseString(`
            @Control({ kind: 'button' })
            class MyScene {
              @Input({ kind: InputKind.Wut })
              public foo: number;
            }
          `);
                }).to.throw(/Invalid InputKind value/);
            });
            it('errors on bad input enum string property', () => {
                chai_1.expect(() => {
                    new extractor_1.MetadataExtractor().parseString(`
            @Control({ kind: 'button' })
            class MyScene {
              @Input({ kind: InputKind.Wut })
              public foo: number;
            }
          `);
                }).to.throw(/Invalid InputKind value/);
            });
            it('errors on complex types', () => {
                chai_1.expect(() => {
                    new extractor_1.MetadataExtractor().parseString(`
            @Control({ kind: someFunc() })
            class MyScene {}
          `);
                }).to.throw(/Only simple, JSON-compatible expressions are allowed here in @Control/);
                chai_1.expect(() => {
                    new extractor_1.MetadataExtractor().parseString(`
            @Control({ kind: class {} })
            class MyScene {}
          `);
                }).to.throw(/Only simple, JSON-compatible expressions are allowed here in @Control/);
            });
        });
    });
    describe('evil sniffer', () => {
        const expectFail = (str) => {
            chai_1.expect(() => new evilsniffer_1.EvilSniffer().parseString(str)).to.throw(/Executing arbitrary code is dangerous/);
        };
        const expectOK = (str) => {
            chai_1.expect(() => new evilsniffer_1.EvilSniffer().parseString(str)).not.to.throw;
        };
        it('detects eval', () => {
            expectFail('eval()');
            expectFail('window.eval()');
        });
        it('detects new func', () => {
            expectFail('new Function()');
            expectFail('new window.Function()');
        });
        it('detect setTimeout/setInterval with strings', () => {
            expectFail('setInterval("foo()")');
            expectFail('window.setInterval("foo()")');
            expectFail('setTimeout("foo()")');
            expectFail('window.setTimeout("foo()")');
            expectFail('setTimeout(`foo()`)');
            expectFail('setTimeout(`f${o}o()`)'); // tslint:disable-line
        });
        it('allows good code/setTimeouts', () => __awaiter(this, void 0, void 0, function* () {
            expectOK('setTimeout(() => foo())');
            expectOK('setTimeout(foo)');
            expectOK(yield util_1.readFile(__filename)); // *universe explodes*
        }));
    });
});
