<commit_before>const fs = require('fs-extra')

const {Fixture} = require('../helper')
const {EventMatcher} = require('../matcher');

[false, true].forEach(poll => {
  describe(`rapid events with poll = ${poll}`, function () {
    let fixture, matcher

    beforeEach(async function () {
      fixture = new Fixture()
      await fixture.before()
      await fixture.log()

      matcher = new EventMatcher(fixture)
      await matcher.watch([], {poll})
    })

    afterEach(async function () {
      await fixture.after(this.currentTest)
    })

    // The polling thread will never be able to distinguish rapid events
    if (!poll) {
      it('understands coalesced creation and deletion events', async function () {
        const deletedPath = fixture.watchPath('deleted.txt')
        const recreatedPath = fixture.watchPath('recreated.txt')
        const createdPath = fixture.watchPath('created.txt')

        await fs.writeFile(deletedPath, 'initial contents\n')
        await until('file creation event arrives', matcher.allEvents(
          {action: 'created', kind: 'file', path: deletedPath}
        ))

        await fs.unlink(deletedPath)
        await fs.writeFile(recreatedPath, 'initial contents\n')
        await fs.unlink(recreatedPath)
        await fs.writeFile(recreatedPath, 'newly created\n')
        await fs.writeFile(createdPath, 'and another\n')

        await until('all events arrive', matcher.orderedEvents(
          {action: 'deleted', kind: 'file', path: deletedPath},
          {action: 'created', kind: 'file', path: recreatedPath},
          {action: 'deleted', kind: 'file', path: recreatedPath},
          {action: 'created', kind: 'file', path: recreatedPath},
          {action: 'created', kind: 'file', path: createdPath}
        ))
      })

      it('understands rapid creation and rename events', async function () {
        const originalPath = fixture.watchPath('created.txt')
        const finalPath = fixture.watchPath('final.txt')

        await fs.writeFile(originalPath, 'contents\n')
        await fs.rename(originalPath, finalPath)

        if (process.platform === 'darwin') {
          await until('creation and deletion events arrive', matcher.orderedEvents(
            {action: 'created', kind: 'file', path: originalPath},
            {action: 'deleted', kind: 'file', path: originalPath},
            {action: 'created', kind: 'file', path: finalPath}
          ))
        } else {
          await until('creation and rename events arrive', matcher.orderedEvents(
            {action: 'created', kind: 'file', path: originalPath},
            {action: 'renamed', kind: 'file', oldPath: originalPath, path: finalPath}
          ))
        }
      })

      it('understands rapid rename and deletion events', async function () {
        const originalPath = fixture.watchPath('created.txt')
        const finalPath = fixture.watchPath('final.txt')

        await fs.writeFile(originalPath, 'contents\n')
        await until('creation event arrives', matcher.allEvents(
          {action: 'created', kind: 'file', path: originalPath}
        ))

        await fs.rename(originalPath, finalPath)
        await fs.remove(finalPath)

        if (process.platform === 'darwin') {
          await until('creation and deletion events arrive', matcher.allEvents(
            {action: 'deleted', kind: 'file', path: originalPath},
            {action: 'created', kind: 'file', path: finalPath},
            {action: 'deleted', kind: 'file', path: finalPath}
          ))
        } else {
          await until('rename and deletion events arrive', matcher.allEvents(
            {action: 'renamed', kind: 'file', oldPath: originalPath, path: finalPath},
            {action: 'deleted', kind: 'file', path: finalPath}
          ))
        }
      })

      it('understands rapid creation, rename, and deletion events', async function () {
        const originalPath = fixture.watchPath('created.txt')
        const finalPath = fixture.watchPath('final.txt')

        await fs.writeFile(originalPath, 'contents\n')
        await fs.rename(originalPath, finalPath)
        await fs.remove(finalPath)

        if (process.platform === 'darwin') {
          await until('creation and deletion events arrive', matcher.allEvents(
            {action: 'created', kind: 'file', path: originalPath},
            {action: 'deleted', kind: 'file', path: originalPath},
            {action: 'created', kind: 'file', path: finalPath},
            {action: 'deleted', kind: 'file', path: finalPath}
          ))
        } else {
          await until('creation, rename, and deletion events arrive', matcher.allEvents(
            {action: 'created', kind: 'file', path: originalPath},
            {action: 'renamed', kind: 'file', oldPath: originalPath, path: finalPath},
            {action: 'deleted', kind: 'file', path: finalPath}
          ))
        }
      })
    }

    it('correlates rapid file rename events', async function () {
      const oldPath0 = fixture.watchPath('old-file-0.txt')
      const oldPath1 = fixture.watchPath('old-file-1.txt')
      const oldPath2 = fixture.watchPath('old-file-2.txt')
      const newPath0 = fixture.watchPath('new-file-0.txt')
      const newPath1 = fixture.watchPath('new-file-1.txt')
      const newPath2 = fixture.watchPath('new-file-2.txt')

      await Promise.all(
        [oldPath0, oldPath1, oldPath2].map(oldPath => fs.writeFile(oldPath, 'original\n'))
      )
      await until('all creation events arrive', matcher.allEvents(
        {action: 'created', kind: 'file', path: oldPath0},
        {action: 'created', kind: 'file', path: oldPath1},
        {action: 'created', kind: 'file', path: oldPath2}
      ))

      await Promise.all([
        fs.rename(oldPath0, newPath0),
        fs.rename(oldPath1, newPath1),
        fs.rename(oldPath2, newPath2)
      ])

      if (poll) {
        await until('all deletion and creation events arrive', matcher.allEvents(
          {action: 'deleted', kind: 'file', path: oldPath0},
          {action: 'deleted', kind: 'file', path: oldPath1},
          {action: 'deleted', kind: 'file', path: oldPath2},
          {action: 'created', kind: 'file', path: newPath0},
          {action: 'created', kind: 'file', path: newPath1},
          {action: 'created', kind: 'file', path: newPath2}
        ))
      } else {
        await until('all rename events arrive', matcher.allEvents(
          {action: 'renamed', kind: 'file', oldPath: oldPath0, path: newPath0},
          {action: 'renamed', kind: 'file', oldPath: oldPath1, path: newPath1},
          {action: 'renamed', kind: 'file', oldPath: oldPath2, path: newPath2}
        ))
      }
    })
  })
})
<commit_msg>Re-drop event kind assertion<commit_after>const fs = require('fs-extra')

const {Fixture} = require('../helper')
const {EventMatcher} = require('../matcher');

[false, true].forEach(poll => {
  describe(`rapid events with poll = ${poll}`, function () {
    let fixture, matcher

    beforeEach(async function () {
      fixture = new Fixture()
      await fixture.before()
      await fixture.log()

      matcher = new EventMatcher(fixture)
      await matcher.watch([], {poll})
    })

    afterEach(async function () {
      await fixture.after(this.currentTest)
    })

    // The polling thread will never be able to distinguish rapid events
    if (!poll) {
      it('understands coalesced creation and deletion events', async function () {
        const deletedPath = fixture.watchPath('deleted.txt')
        const recreatedPath = fixture.watchPath('recreated.txt')
        const createdPath = fixture.watchPath('created.txt')

        await fs.writeFile(deletedPath, 'initial contents\n')
        await until('file creation event arrives', matcher.allEvents(
          {action: 'created', kind: 'file', path: deletedPath}
        ))

        await fs.unlink(deletedPath)
        await fs.writeFile(recreatedPath, 'initial contents\n')
        await fs.unlink(recreatedPath)
        await fs.writeFile(recreatedPath, 'newly created\n')
        await fs.writeFile(createdPath, 'and another\n')

        await until('all events arrive', matcher.orderedEvents(
          {action: 'deleted', kind: 'file', path: deletedPath},
          {action: 'created', path: recreatedPath},
          {action: 'deleted', path: recreatedPath},
          {action: 'created', kind: 'file', path: recreatedPath},
          {action: 'created', kind: 'file', path: createdPath}
        ))
      })

      it('understands rapid creation and rename events', async function () {
        const originalPath = fixture.watchPath('created.txt')
        const finalPath = fixture.watchPath('final.txt')

        await fs.writeFile(originalPath, 'contents\n')
        await fs.rename(originalPath, finalPath)

        if (process.platform === 'darwin') {
          await until('creation and deletion events arrive', matcher.orderedEvents(
            {action: 'created', kind: 'file', path: originalPath},
            {action: 'deleted', kind: 'file', path: originalPath},
            {action: 'created', kind: 'file', path: finalPath}
          ))
        } else {
          await until('creation and rename events arrive', matcher.orderedEvents(
            {action: 'created', kind: 'file', path: originalPath},
            {action: 'renamed', kind: 'file', oldPath: originalPath, path: finalPath}
          ))
        }
      })

      it('understands rapid rename and deletion events', async function () {
        const originalPath = fixture.watchPath('created.txt')
        const finalPath = fixture.watchPath('final.txt')

        await fs.writeFile(originalPath, 'contents\n')
        await until('creation event arrives', matcher.allEvents(
          {action: 'created', kind: 'file', path: originalPath}
        ))

        await fs.rename(originalPath, finalPath)
        await fs.remove(finalPath)

        if (process.platform === 'darwin') {
          await until('creation and deletion events arrive', matcher.allEvents(
            {action: 'deleted', kind: 'file', path: originalPath},
            {action: 'created', kind: 'file', path: finalPath},
            {action: 'deleted', kind: 'file', path: finalPath}
          ))
        } else {
          await until('rename and deletion events arrive', matcher.allEvents(
            {action: 'renamed', kind: 'file', oldPath: originalPath, path: finalPath},
            {action: 'deleted', kind: 'file', path: finalPath}
          ))
        }
      })

      it('understands rapid creation, rename, and deletion events', async function () {
        const originalPath = fixture.watchPath('created.txt')
        const finalPath = fixture.watchPath('final.txt')

        await fs.writeFile(originalPath, 'contents\n')
        await fs.rename(originalPath, finalPath)
        await fs.remove(finalPath)

        if (process.platform === 'darwin') {
          await until('creation and deletion events arrive', matcher.allEvents(
            {action: 'created', kind: 'file', path: originalPath},
            {action: 'deleted', kind: 'file', path: originalPath},
            {action: 'created', kind: 'file', path: finalPath},
            {action: 'deleted', kind: 'file', path: finalPath}
          ))
        } else {
          await until('creation, rename, and deletion events arrive', matcher.allEvents(
            {action: 'created', kind: 'file', path: originalPath},
            {action: 'renamed', kind: 'file', oldPath: originalPath, path: finalPath},
            {action: 'deleted', kind: 'file', path: finalPath}
          ))
        }
      })
    }

    it('correlates rapid file rename events', async function () {
      const oldPath0 = fixture.watchPath('old-file-0.txt')
      const oldPath1 = fixture.watchPath('old-file-1.txt')
      const oldPath2 = fixture.watchPath('old-file-2.txt')
      const newPath0 = fixture.watchPath('new-file-0.txt')
      const newPath1 = fixture.watchPath('new-file-1.txt')
      const newPath2 = fixture.watchPath('new-file-2.txt')

      await Promise.all(
        [oldPath0, oldPath1, oldPath2].map(oldPath => fs.writeFile(oldPath, 'original\n'))
      )
      await until('all creation events arrive', matcher.allEvents(
        {action: 'created', kind: 'file', path: oldPath0},
        {action: 'created', kind: 'file', path: oldPath1},
        {action: 'created', kind: 'file', path: oldPath2}
      ))

      await Promise.all([
        fs.rename(oldPath0, newPath0),
        fs.rename(oldPath1, newPath1),
        fs.rename(oldPath2, newPath2)
      ])

      if (poll) {
        await until('all deletion and creation events arrive', matcher.allEvents(
          {action: 'deleted', kind: 'file', path: oldPath0},
          {action: 'deleted', kind: 'file', path: oldPath1},
          {action: 'deleted', kind: 'file', path: oldPath2},
          {action: 'created', kind: 'file', path: newPath0},
          {action: 'created', kind: 'file', path: newPath1},
          {action: 'created', kind: 'file', path: newPath2}
        ))
      } else {
        await until('all rename events arrive', matcher.allEvents(
          {action: 'renamed', kind: 'file', oldPath: oldPath0, path: newPath0},
          {action: 'renamed', kind: 'file', oldPath: oldPath1, path: newPath1},
          {action: 'renamed', kind: 'file', oldPath: oldPath2, path: newPath2}
        ))
      }
    })
  })
})
<|endoftext|>


<commit_before>var createContext = require('./util/create-context')
var createREGL = require('../../regl')
var tape = require('tape')

tape('framebuffer parsing', function (t) {
  var gl = createContext(16, 16)
  var regl = createREGL({
    gl: gl,
    optionalExtensions: [
      'webgl_draw_buffers',
      'oes_texture_float',
      'oes_texture_half_float',
      'webgl_color_buffer_float',
      'ext_color_buffer_half_float',
      'ext_srgb']
  })

  function checkProperties (framebuffer, props, prefix) {
    var _framebuffer = framebuffer._framebuffer

    t.equals(framebuffer.width, props.width, prefix + '.width')
    t.equals(framebuffer.height, props.height, prefix + '.height')
    t.equals(_framebuffer.width, props.width, prefix + '.width')
    t.equals(_framebuffer.height, props.height, prefix + '.height')

    gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer.framebuffer)

    function diffAttachments (actual, expected, sprefix, attachment) {
      var label = prefix + ' ' + sprefix

      function getParameter (pname) {
        return gl.getFramebufferAttachmentParameter(
          gl.FRAMEBUFFER,
          attachment,
          pname)
      }

      if (!expected) {
        t.equals(actual, null, label + ' is null')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE),
          0,
          label + ' object type')
        return
      }

      t.equals(actual.target, expected.target, label + '.target')

      if (expected.target === gl.RENDERBUFFER) {
        t.equals(actual.texture, null, label + '.texture')
        t.equals(
          actual.renderbuffer._renderbuffer.format,
          expected.format,
          label + '.format')
        t.equals(actual.renderbuffer.width, props.width, label + '.width')
        t.equals(actual.renderbuffer.height, props.height, label + '.height')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE),
          gl.RENDERBUFFER,
          label + ' object type')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME),
          actual.renderbuffer._renderbuffer.renderbuffer,
          label + ' object assoc')
      } else {
        t.equals(actual.renderbuffer, null, label + '.renderbuffer')
        t.equals(
          actual.texture._texture.internalformat,
          expected.format,
          label + '.format')
        t.equals(
          actual.texture._texture.type,
          expected.type,
          label + '.type')
        t.equals(actual.texture.width, props.width, label + '.width')
        t.equals(actual.texture.height, props.height, label + '.height')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE),
          gl.TEXTURE,
          label + ' object type')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME),
          actual.texture._texture.texture,
          label + ' object assoc')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL),
          0,
          label + ' miplevel')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE),
          expected.target === gl.TEXTURE_2D ? 0 : expected.target,
          label + ' cube face')
      }
    }

    if (props.color) {
      t.equals(
        framebuffer.color.length,
        props.color.length,
        prefix + ' colorCount')
      for (var i = 0; i < props.color.length; ++i) {
        diffAttachments(
          _framebuffer.colorAttachments[i] || _framebuffer.colorAttachments,
          props.color[i],
          'color[' + i + ']',
          gl.COLOR_ATTACHMENT0 + i)
      }
    }

    diffAttachments(
      _framebuffer.depthAttachment,
      props.depth || null,
      'depth',
      gl.DEPTH_ATTACHMENT)
    diffAttachments(
      _framebuffer.stencilAttachment,
      props.stencil || null,
      'stencil',
      gl.STENCIL_ATTACHMENT)
    diffAttachments(
      _framebuffer.depthStencilAttachment,
      props.depthStencil || null,
      'depth stencil',
      gl.DEPTH_STENCIL_ATTACHMENT)
  }

  // empty constructor
  checkProperties(
    regl.framebuffer(),
    {
      width: 1,
      height: 1,
      color: [{
        target: gl.TEXTURE_2D,
        format: gl.RGBA,
        type: gl.UNSIGNED_BYTE
      }],
      depthStencil: {
        target: gl.RENDERBUFFER,
        format: gl.DEPTH_STENCIL
      }
    },
    'empty')

  checkProperties(
    regl.framebuffer({
      width: 5,
      height: 5,
      depth: false
    }),
    {
      width: 5,
      height: 5,
      color: [{
        target: gl.TEXTURE_2D,
        format: gl.RGBA,
        type: gl.UNSIGNED_BYTE
      }],
      stencil: {
        target: gl.RENDERBUFFER,
        format: gl.STENCIL_INDEX8
      }
    },
    'shape and no depth')

  checkProperties(
    regl.framebuffer({
      width: 10,
      height: 10,
      colorFormat: 'rgb5 a1',
      depth: true,
      stencil: true
    }),
    {
      width: 10,
      height: 10,
      color: [{
        target: gl.RENDERBUFFER,
        format: gl.RGB5_A1
      }],
      depthStencil: {
        target: gl.RENDERBUFFER,
        format: gl.DEPTH_STENCIL
      }
    },
    'color renderbuffer')

  checkProperties(
    regl.framebuffer({
      shape: [10, 10],
      colorTexture: false,
      depth: true,
      stencil: true
    }),
    {
      width: 10,
      height: 10,
      color: [{
        target: gl.RENDERBUFFER,
        format: gl.RGBA4
      }],
      depthStencil: {
        target: gl.RENDERBUFFER,
        format: gl.DEPTH_STENCIL
      }
    },
    'color renderbuffer')

  // next, we will 'colorType' and 'colorFormat'. We test for all possible combinations of these values.

  var testCases = [
    {tex: true, colorFormat: 'rgba', colorType: 'uint8', expectedFormat: gl.RGBA, expectedType: gl.UNSIGNED_BYTE},
    {tex: false, colorFormat: 'rgba4', expectedFormat: gl.RGBA4},
    {tex: false, colorFormat: 'rgb565', expectedFormat: gl.RGB565},
    {tex: false, colorFormat: 'rgb5 a1', expectedFormat: gl.RGB5_A1}
  ]

  if (regl.hasExtension('oes_texture_float')) {
    testCases.push({tex: true, colorFormat: 'rgba', colorType: 'float', expectedFormat: gl.RGBA, expectedType: gl.FLOAT})
   // testCases.push({tex: true, colorFormat: 'rgb', colorType: 'float', expectedFormat: gl.RGB, expectedType: gl.FLOAT})
  }

  if (regl.hasExtension('oes_texture_half_float')) {
    var GL_HALF_FLOAT_OES = 0x8D61
    testCases.push({tex: true, colorFormat: 'rgba', colorType: 'half float', expectedFormat: gl.RGBA, expectedType: GL_HALF_FLOAT_OES})
//    testCases.push({tex: true, colorFormat: 'rgb', colorType: 'half float', expectedFormat: gl.RGB, expectedType: GL_HALF_FLOAT_OES})
  }

  // We'll skip testing the renderbuffer formats rgba32f, rgba16f, rgb16f.
  // Because the extensions 'ext_color_buffer_half_float' and
  // 'webgl_color_buffer_float' have really spotty browser support.
  // For me, they are available in Firefox, but not in Chrome, for some reason.

  if (regl.hasExtension('ext_srgb')) {
    var GL_SRGB8_ALPHA8_EXT = 0x8C43
    testCases.push({tex: false, colorFormat: 'srgba', expectedFormat: GL_SRGB8_ALPHA8_EXT})
  }

  testCases.forEach(function (testCase, i) {
    var fboArgs = {
      shape: [10, 10],
      colorFormat: testCase.colorFormat
    }

    var expected
    if (testCase.tex) {
      expected = {
        target: gl.TEXTURE_2D,
        format: testCase.expectedFormat,
        type: testCase.expectedType
      }
      fboArgs.colorType = testCase.colorType
    } else {
      expected = {
        target: gl.RENDERBUFFER,
        format: testCase.expectedFormat
      }
    }

    checkProperties(
      regl.framebuffer(fboArgs),
      {
        width: 10,
        height: 10,
        color: [expected],
        depthStencil: {
          target: gl.RENDERBUFFER,
          format: gl.DEPTH_STENCIL
        }
      },
      'for colorFormat=' + testCase.colorFormat + (testCase.tex ? (' and colorType=' + testCase.colorType) : ''))
  })

  if (
    regl.hasExtension('WEBGL_draw_buffers') && regl.hasExtension('oes_texture_float') && regl.hasExtension('oes_texture_half_float')) {
    t.throws(function () {
      regl.framebuffer({
        color: [
          regl.texture({type: 'float'}),
          regl.texture({type: 'uint8'})
        ]}) },
             /\(regl\)/,
             '#1 check color attachments with different bit planes throws')

    t.throws(function () {
      regl.framebuffer({
        color: [
          regl.texture({type: 'uint8', format: 'rgb'}),
          regl.texture({type: 'float', format: 'rgba'})
        ]}) },
             /\(regl\)/,
             '#2 check color attachments with different bit planes throws')

    t.throws(function () {
      regl.framebuffer({
        color: [
          regl.texture({type: 'half float', format: 'rgb'}),
          regl.texture({type: 'uint8', format: 'rgba'})
        ]}) },
             /\(regl\)/,
             '#3 check color attachments with different bit planes throws')

    var thrown = false
    try {
      regl.framebuffer({
        color: [
          regl.texture({type: 'float'}),
          regl.texture({type: 'float'})
        ]})
    } catch (e) {
      thrown = true
    }

    t.equals(thrown, false, 'check color attachments with same bit planes do not throw')
  }

  // TODO: multiple render targets

  regl.destroy()
  createContext.destroy(gl)
  t.end()
})
<commit_msg>test/framebuffer-parse.js: Now testing that `regl.framebuffer` throws if an invalid colorFormat is used.<commit_after>var createContext = require('./util/create-context')
var createREGL = require('../../regl')
var tape = require('tape')

tape('framebuffer parsing', function (t) {
  var gl = createContext(16, 16)
  var regl = createREGL({
    gl: gl,
    optionalExtensions: [
      'webgl_draw_buffers',
      'oes_texture_float',
      'oes_texture_half_float',
      'webgl_color_buffer_float',
      'ext_color_buffer_half_float',
      'ext_srgb']
  })

  function checkProperties (framebuffer, props, prefix) {
    var _framebuffer = framebuffer._framebuffer

    t.equals(framebuffer.width, props.width, prefix + '.width')
    t.equals(framebuffer.height, props.height, prefix + '.height')
    t.equals(_framebuffer.width, props.width, prefix + '.width')
    t.equals(_framebuffer.height, props.height, prefix + '.height')

    gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer.framebuffer)

    function diffAttachments (actual, expected, sprefix, attachment) {
      var label = prefix + ' ' + sprefix

      function getParameter (pname) {
        return gl.getFramebufferAttachmentParameter(
          gl.FRAMEBUFFER,
          attachment,
          pname)
      }

      if (!expected) {
        t.equals(actual, null, label + ' is null')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE),
          0,
          label + ' object type')
        return
      }

      t.equals(actual.target, expected.target, label + '.target')

      if (expected.target === gl.RENDERBUFFER) {
        t.equals(actual.texture, null, label + '.texture')
        t.equals(
          actual.renderbuffer._renderbuffer.format,
          expected.format,
          label + '.format')
        t.equals(actual.renderbuffer.width, props.width, label + '.width')
        t.equals(actual.renderbuffer.height, props.height, label + '.height')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE),
          gl.RENDERBUFFER,
          label + ' object type')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME),
          actual.renderbuffer._renderbuffer.renderbuffer,
          label + ' object assoc')
      } else {
        t.equals(actual.renderbuffer, null, label + '.renderbuffer')
        t.equals(
          actual.texture._texture.internalformat,
          expected.format,
          label + '.format')
        t.equals(
          actual.texture._texture.type,
          expected.type,
          label + '.type')
        t.equals(actual.texture.width, props.width, label + '.width')
        t.equals(actual.texture.height, props.height, label + '.height')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE),
          gl.TEXTURE,
          label + ' object type')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME),
          actual.texture._texture.texture,
          label + ' object assoc')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL),
          0,
          label + ' miplevel')
        t.equals(
          getParameter(gl.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE),
          expected.target === gl.TEXTURE_2D ? 0 : expected.target,
          label + ' cube face')
      }
    }

    if (props.color) {
      t.equals(
        framebuffer.color.length,
        props.color.length,
        prefix + ' colorCount')
      for (var i = 0; i < props.color.length; ++i) {
        diffAttachments(
          _framebuffer.colorAttachments[i] || _framebuffer.colorAttachments,
          props.color[i],
          'color[' + i + ']',
          gl.COLOR_ATTACHMENT0 + i)
      }
    }

    diffAttachments(
      _framebuffer.depthAttachment,
      props.depth || null,
      'depth',
      gl.DEPTH_ATTACHMENT)
    diffAttachments(
      _framebuffer.stencilAttachment,
      props.stencil || null,
      'stencil',
      gl.STENCIL_ATTACHMENT)
    diffAttachments(
      _framebuffer.depthStencilAttachment,
      props.depthStencil || null,
      'depth stencil',
      gl.DEPTH_STENCIL_ATTACHMENT)
  }

  // empty constructor
  checkProperties(
    regl.framebuffer(),
    {
      width: 1,
      height: 1,
      color: [{
        target: gl.TEXTURE_2D,
        format: gl.RGBA,
        type: gl.UNSIGNED_BYTE
      }],
      depthStencil: {
        target: gl.RENDERBUFFER,
        format: gl.DEPTH_STENCIL
      }
    },
    'empty')

  checkProperties(
    regl.framebuffer({
      width: 5,
      height: 5,
      depth: false
    }),
    {
      width: 5,
      height: 5,
      color: [{
        target: gl.TEXTURE_2D,
        format: gl.RGBA,
        type: gl.UNSIGNED_BYTE
      }],
      stencil: {
        target: gl.RENDERBUFFER,
        format: gl.STENCIL_INDEX8
      }
    },
    'shape and no depth')

  checkProperties(
    regl.framebuffer({
      width: 10,
      height: 10,
      colorFormat: 'rgb5 a1',
      depth: true,
      stencil: true
    }),
    {
      width: 10,
      height: 10,
      color: [{
        target: gl.RENDERBUFFER,
        format: gl.RGB5_A1
      }],
      depthStencil: {
        target: gl.RENDERBUFFER,
        format: gl.DEPTH_STENCIL
      }
    },
    'color renderbuffer')

  checkProperties(
    regl.framebuffer({
      shape: [10, 10],
      colorTexture: false,
      depth: true,
      stencil: true
    }),
    {
      width: 10,
      height: 10,
      color: [{
        target: gl.RENDERBUFFER,
        format: gl.RGBA4
      }],
      depthStencil: {
        target: gl.RENDERBUFFER,
        format: gl.DEPTH_STENCIL
      }
    },
    'color renderbuffer')

  // next, we will 'colorType' and 'colorFormat'. We test for all possible combinations of these values.

  var testCases = [
    {tex: true, colorFormat: 'rgba', colorType: 'uint8', expectedFormat: gl.RGBA, expectedType: gl.UNSIGNED_BYTE},
    {tex: false, colorFormat: 'rgba4', expectedFormat: gl.RGBA4},
    {tex: false, colorFormat: 'rgb565', expectedFormat: gl.RGB565},
    {tex: false, colorFormat: 'rgb5 a1', expectedFormat: gl.RGB5_A1}
  ]

  // these test cases should fail.
  var badTestCases = [
    {colorFormat: 'alpha', colorType: 'uint8'},
    {colorFormat: 'luminance', colorType: 'uint8'},
    {colorFormat: 'luminance alpha', colorType: 'uint8'}
  ]

  if (regl.hasExtension('oes_texture_float')) {
    testCases.push({tex: true, colorFormat: 'rgba', colorType: 'float', expectedFormat: gl.RGBA, expectedType: gl.FLOAT})
    badTestCases.push({colorFormat: 'rgb', colorType: 'float'})
  }

  if (regl.hasExtension('oes_texture_half_float')) {
    var GL_HALF_FLOAT_OES = 0x8D61
    testCases.push({tex: true, colorFormat: 'rgba', colorType: 'half float', expectedFormat: gl.RGBA, expectedType: GL_HALF_FLOAT_OES})
    badTestCases.push({colorFormat: 'rgb', colorType: 'half float'})
  }

  // We'll skip testing the renderbuffer formats rgba32f, rgba16f, rgb16f.
  // Because the extensions 'ext_color_buffer_half_float' and
  // 'webgl_color_buffer_float' have really spotty browser support.
  // For me, they are available in Firefox, but not in Chrome, for some reason.

  if (regl.hasExtension('ext_srgb')) {
    var GL_SRGB8_ALPHA8_EXT = 0x8C43
    testCases.push({tex: false, colorFormat: 'srgba', expectedFormat: GL_SRGB8_ALPHA8_EXT})
  }

  testCases.forEach(function (testCase, i) {
    var fboArgs = {
      shape: [10, 10],
      colorFormat: testCase.colorFormat
    }

    var expected
    if (testCase.tex) {
      expected = {
        target: gl.TEXTURE_2D,
        format: testCase.expectedFormat,
        type: testCase.expectedType
      }
      fboArgs.colorType = testCase.colorType
    } else {
      expected = {
        target: gl.RENDERBUFFER,
        format: testCase.expectedFormat
      }
    }

    checkProperties(
      regl.framebuffer(fboArgs),
      {
        width: 10,
        height: 10,
        color: [expected],
        depthStencil: {
          target: gl.RENDERBUFFER,
          format: gl.DEPTH_STENCIL
        }
      },
      'for colorFormat=' + testCase.colorFormat + (testCase.tex ? (' and colorType=' + testCase.colorType) : ''))
  })

  badTestCases.forEach(function (testCase, i) {
    var fboArgs = {
      shape: [10, 10],
      colorFormat: testCase.colorFormat,
      colorType: testCase.colorType
    }

    t.throws(
      function () { regl.framebuffer(fboArgs) },
        /\(regl\)/,
      'throws for colorFormat=' + testCase.colorFormat + ' and colorType=' + testCase.colorType)
  })

  if (
    regl.hasExtension('WEBGL_draw_buffers') && regl.hasExtension('oes_texture_float') && regl.hasExtension('oes_texture_half_float')) {
    t.throws(function () {
      regl.framebuffer({
        color: [
          regl.texture({type: 'float'}),
          regl.texture({type: 'uint8'})
        ]}) },
             /\(regl\)/,
             '#1 check color attachments with different bit planes throws')

    t.throws(function () {
      regl.framebuffer({
        color: [
          regl.texture({type: 'uint8', format: 'rgb'}),
          regl.texture({type: 'float', format: 'rgba'})
        ]}) },
             /\(regl\)/,
             '#2 check color attachments with different bit planes throws')

    t.throws(function () {
      regl.framebuffer({
        color: [
          regl.texture({type: 'half float', format: 'rgb'}),
          regl.texture({type: 'uint8', format: 'rgba'})
        ]}) },
             /\(regl\)/,
             '#3 check color attachments with different bit planes throws')

    var thrown = false
    try {
      regl.framebuffer({
        color: [
          regl.texture({type: 'float'}),
          regl.texture({type: 'float'})
        ]})
    } catch (e) {
      thrown = true
    }

    t.equals(thrown, false, 'check color attachments with same bit planes do not throw')
  }

  // TODO: multiple render targets

  regl.destroy()
  createContext.destroy(gl)
  t.end()
})
<|endoftext|>


<commit_before>"use strict";

var should = require('should')

function Cell(x, y) {
  this.x = x
  this.y = y

  var alive = false

  this.isAlive = function () {
    return alive
  }

  this.revive = function () {
    alive = true
  }

  this.die = function () {
    alive = false
  }

}

Cell.prototype.applyRules = function () {
  if (this.neighbours < 2) return this.die()
  if (this.neighbours === 3) return this.revive()
  if (this.neighbours > 3) return this.die()
}

function overflowPosition(value, max) {
  if (value > max) {
    return 0
  } else if (value < 0) {
    return max
  } else {
    return value
  }
}

function Board(numRows, numColumns) {
  var rows = []
  var that = this

  function createRow(x) {
    var row = []
    for (var y = 0; y < numColumns; y++) {
      row.push(new Cell(x, y))
    }
    return row
  }

  for (var x = 0; x < numRows; x++) {
    rows.push(createRow(x))
  }

  this.at = function (x, y) {
    return rows[x][y]
  }

  var allCells = []
  allCells = [].concat.apply(allCells, rows);

  this.assignNeighbourCount = function () {
    allCells.forEach(function (cell) {
      cell.neighbours = neighbourCellsFor(cell).reduce(function (acc, cell) {
        if (cell.isAlive()) acc++
        return acc
      }, 0)
    })
  }

  this.applyRules = function () {
    allCells.forEach(function (cell) {
      cell.applyRules()
    })
  }

  function neighbourCellsFor(cell) {
    var maxRowIndex = numRows - 1
    var maxColumnIndex = numColumns - 1

    return Board.RELATIVE_NEIGHBOURS.reduce(function (neighbourCells, position) {
      var dx = overflowPosition(cell.x + position.x, maxRowIndex)
      var dy = overflowPosition(cell.y + position.y, maxColumnIndex)
      neighbourCells.push(that.at(dx, dy))
      return neighbourCells
    }, [])
  }
}

Board.prototype.step = function () {
  this.assignNeighbourCount()
  this.applyRules()
}

Board.RELATIVE_NEIGHBOURS = [
  {x: -1, y: -1}, {x: -1, y: 0}, {x: -1, y: 1},
  {x: 0, y: -1},                 {x: 0, y: 1},
  {x: 1, y: -1},  {x: 1, y: 0},  {x: 1, y: 1}
]

describe("Game of Life", function () {
  describe("Cell rules", function () {
    var cell

    beforeEach(function () {
      cell = new Cell()
    })

    it("dies with less then 2 living neighbors", function () {
      cell.revive()
      cell.neighbours = 1
      cell.applyRules()
      cell.isAlive().should.be.not.be.true
    })

    it("is revived with exactly 3 living neighbours", function () {
      cell.neighbours = 3
      cell.applyRules()
      cell.isAlive().should.be.true
    })

    it("dies with more than 3 living neighbours", function () {
      cell.revive()
      cell.neighbours = 4
      cell.applyRules()
      cell.isAlive().should.not.be.true
    })
  })

  describe("Board", function () {
    it("applies next step", function () {
      var board = new Board(3, 3)
      board.at(0, 0).revive()
      board.at(1, 1).revive()
      board.at(1, 2).revive()
      board.step()
      board.at(0, 0).isAlive().should.be.true
      board.at(1, 1).isAlive().should.be.true
      board.at(2, 2).isAlive().should.be.true
    })
  })
})

<commit_msg>added some printing<commit_after>"use strict";

var should = require('should')

function Cell(x, y) {
  this.x = x
  this.y = y

  var alive = false

  this.isAlive = function () {
    return alive
  }

  this.revive = function () {
    alive = true
  }

  this.die = function () {
    alive = false
  }

  this.toString = function () {
    if (alive)
      return "*"
    else
      return "."
  }

}

Cell.prototype.applyRules = function () {
  if (this.neighbours < 2) return this.die()
  if (this.neighbours === 3) return this.revive()
  if (this.neighbours > 3) return this.die()
}

function overflowPosition(value, max) {
  if (value > max) {
    return 0
  } else if (value < 0) {
    return max
  } else {
    return value
  }
}

function Board(numRows, numColumns) {
  var rows = []
  var that = this
  var iteration = 1

  this.print = function () {
    console.log("Current Iteration: ", iteration++)
    rows.forEach(function (row) {
      var str = row.reduce(function (str, cell) {
        return str += cell
      }, "")
      console.log(str)
    })
  }

  function createRow(x) {
    var row = []
    for (var y = 0; y < numColumns; y++) {
      row.push(new Cell(x, y))
    }
    return row
  }

  for (var x = 0; x < numRows; x++) {
    rows.push(createRow(x))
  }

  this.at = function (x, y) {
    return rows[x][y]
  }

  var allCells = []
  allCells = [].concat.apply(allCells, rows);

  this.assignNeighbourCount = function () {
    allCells.forEach(function (cell) {
      cell.neighbours = neighbourCellsFor(cell).reduce(function (acc, cell) {
        if (cell.isAlive()) acc++
        return acc
      }, 0)
    })
  }

  this.applyRules = function () {
    allCells.forEach(function (cell) {
      cell.applyRules()
    })
  }

  function neighbourCellsFor(cell) {
    var maxRowIndex = numRows - 1
    var maxColumnIndex = numColumns - 1

    return Board.RELATIVE_NEIGHBOURS.reduce(function (neighbourCells, position) {
      var dx = overflowPosition(cell.x + position.x, maxRowIndex)
      var dy = overflowPosition(cell.y + position.y, maxColumnIndex)
      neighbourCells.push(that.at(dx, dy))
      return neighbourCells
    }, [])
  }
}

Board.prototype.step = function () {
  this.assignNeighbourCount()
  this.applyRules()
}

Board.RELATIVE_NEIGHBOURS = [
  {x: -1, y: -1}, {x: -1, y: 0}, {x: -1, y: 1},
  {x: 0, y: -1},                 {x: 0, y: 1},
  {x: 1, y: -1},  {x: 1, y: 0},  {x: 1, y: 1}
]

describe("Game of Life", function () {
  describe("Cell rules", function () {
    var cell

    beforeEach(function () {
      cell = new Cell()
    })

    it("dies with less then 2 living neighbors", function () {
      cell.revive()
      cell.neighbours = 1
      cell.applyRules()
      cell.isAlive().should.be.not.be.true
    })

    it("is revived with exactly 3 living neighbours", function () {
      cell.neighbours = 3
      cell.applyRules()
      cell.isAlive().should.be.true
    })

    it("dies with more than 3 living neighbours", function () {
      cell.revive()
      cell.neighbours = 4
      cell.applyRules()
      cell.isAlive().should.not.be.true
    })
  })

  describe("Board", function () {
    it("applies next step", function () {
      var board = new Board(3, 3)
      board.at(0, 0).revive()
      board.at(1, 1).revive()
      board.at(1, 2).revive()
      board.step()
      board.at(0, 0).isAlive().should.be.true
      board.at(1, 1).isAlive().should.be.true
      board.at(2, 2).isAlive().should.be.true
    })
  })
})

var board = new Board(5, 5)
board.at(1, 0).revive()
board.at(1, 1).revive()
board.at(1, 2).revive()
for (var i = 0; i < 10; i++) {
  board.print()
  board.step()
}

<|endoftext|>


<commit_before>'use strict';

var DebugLogtron = require('debug-logtron');

var AutobahnClient = require('../');

var allocCluster = require('autobahn/test/lib/test-cluster.js');
var TChannelJSON = require('tchannel/as/json');

allocCluster.test('register and forward', {
    size: 5
}, function t(cluster, assert) {
    var steve = cluster.remotes.steve;
    var bob = cluster.remotes.bob;

    var tchannelJSON = TChannelJSON({
        logger: cluster.logger
    });

    var steveAutobahnClient = new AutobahnClient({
        serviceName: steve.serviceName,
        callerName: 'forward-test',
        hostPortList: cluster.hostPortList,
        tchannel: steve.channel,
        logger: DebugLogtron('autobahnClient')
    });
    steveAutobahnClient.once('registered', onRegistered);
    steveAutobahnClient.register();

    function onRegistered() {
        var result = steveAutobahnClient.latestRegistrationResult;

        assert.equal(result.head, null, 'header is null');
        assert.ok(result.body, 'got a body');

        assert.equal(typeof result.body.connectionCount, 'number');

        tchannelJSON.send(bob.clientChannel.request({
            timeout: 5000,
            serviceName: steve.serviceName
        }), 'echo', null, 'oh hi lol', onForwarded);

    }

    function onForwarded(err, resp) {
        assert.ifError(err);
        assert.equal(String(resp.body), 'oh hi lol');

        steveAutobahnClient.destroy();
        assert.end();
    }
});
<commit_msg>test/hyperbahn: make the forward test work<commit_after>'use strict';

var DebugLogtron = require('debug-logtron');

var HyperbahnClient = require('../../hyperbahn/index.js');
var HyperbahnCluster = require('../lib/hyperbahn-cluster.js');
var TChannelJSON = require('../../as/json');

HyperbahnCluster.test('register and forward', {
    size: 5
}, function t(cluster, assert) {
    var steve = cluster.remotes.steve;
    var bob = cluster.remotes.bob;

    var tchannelJSON = TChannelJSON({
        logger: cluster.logger
    });

    var steveAutobahnClient = new HyperbahnClient({
        serviceName: steve.serviceName,
        callerName: 'forward-test',
        hostPortList: cluster.hostPortList,
        tchannel: steve.channel,
        logger: DebugLogtron('autobahnClient')
    });
    steveAutobahnClient.once('registered', onRegistered);
    steveAutobahnClient.register();

    function onRegistered() {
        var result = steveAutobahnClient.latestRegistrationResult;

        assert.equal(result.head, null, 'header is null');
        assert.ok(result.body, 'got a body');

        assert.equal(typeof result.body.connectionCount, 'number');

        tchannelJSON.send(bob.clientChannel.request({
            timeout: 5000,
            serviceName: steve.serviceName
        }), 'echo', null, 'oh hi lol', onForwarded);

    }

    function onForwarded(err, resp) {
        assert.ifError(err);
        assert.equal(String(resp.body), 'oh hi lol');

        steveAutobahnClient.destroy();
        assert.end();
    }
});
<|endoftext|>


<commit_before>import Resource from '../../../dist/lib/model/resource';
import Relationship from '../../../dist/lib/model/relationship';
import ResourceArray from '../../../dist/lib/model/resource-array';
import Promise from 'bluebird';

export default (t, app) => new Promise(resolve => {
  t.test(`app.model('person').find`, assert => {
    app.model('person').find().then(people => {
      assert.ok(people instanceof ResourceArray, 'found people is a resource array');
      assert.equal(people.resources.length, 0, 'no people were found');
      assert.equal(people.count, 0, 'count property is zero');
      assert.end();
    });
  });

  t.test(`app.model.create`, assert => {
    app.model('person').create({
      name: 'Bobby Jones',
      age: 22,
      email: 'bjones@gmail.com',
    }).then(person => {
      assert.ok(person instanceof Resource, 'created person is a resource');
      assert.equal(person.get('name'), 'Bobby Jones', 'name attribute is good');
      assert.equal(person.get('email'), 'bjones@gmail.com', 'email attribute is good');
      assert.equal(person.get('age'), 22, 'age attribute is good');

      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'person pets is a resource array');
      assert.equal(pets.length, 0, 'person pets has zero length');

      return app.model('animal').create({
        species: 'Dog',
        age: 10,
        owner: 1,
      });
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'created animal is a resource');
      assert.equal(animal.get('species'), 'Dog', 'species attribute is good');
      assert.equal(animal.get('age'), 10, 'age attribute is good');

      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'animal owner is a resource');
      assert.equal(owner.get('name'), 'Bobby Jones', 'animal owner name attribute is good');
      assert.equal(owner.get('email'), 'bjones@gmail.com', 'animal owner email attribute is good');
      assert.equal(owner.get('age'), 22, 'animal owner age attribute is good');

      return app.model('person').create({
        name: 'Joe',
        age: 45,
        email: 'joe@gmail.com',
        pets: [ 1 ],
      });
    }).then(person => {
      assert.ok(person instanceof Resource, 'person is a resource');

      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'person pets is a resource array');
      assert.equal(pets.length, 1, 'person pets has length of 1');
      assert.end();
    });
  });

  t.test(`app.model.update`, assert => {
    app.model('animal').update(1, {
      age: 11,
      owner: 2,
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'updated animal is a resource');
      assert.equal(animal.get('species'), 'Dog', 'species attribute is good');
      assert.equal(animal.get('age'), 11, 'age attribute is good');

      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'animal owner is a resource');
      assert.equal(owner.get('name'), 'Joe', 'animal owner name attribute is good');
      assert.equal(owner.get('email'), 'joe@gmail.com', 'animal owner email attribute is good');
      assert.equal(owner.get('age'), 45, 'animal owner age attribute is good');

      return app.model('animal').update(1, {
        owner: null,
      });
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'updated animal is a resource');
      assert.equal(animal.get('species'), 'Dog', 'species attribute is good');
      assert.equal(animal.get('age'), 11, 'age attribute is good');

      return animal.get('owner');
    }).then(owner => {
      assert.equal(owner, null, 'owner is null');

      return app.model('person').update(2, {
        pets: [],
      });
    }).then(person => {
      assert.ok(person instanceof Resource, 'person is a resource');
      assert.equal(person.get('name'), 'Joe', 'person name attribute is good');
      assert.equal(person.get('email'), 'joe@gmail.com', 'person email attribute is good');
      assert.equal(person.get('age'), 45, 'person age attribute is good');

      return person.get('pets');
    }).then(pets => {
      assert.deepEqual(pets.resources, [], 'pets is an empty array');
      assert.end();
    });
  });

  t.test(`app.model.delete`, assert => {
    app.model('animal').delete(1).then(success => {
      assert.ok(success, 'success is true');
      assert.equal(app.adapter.store.animal.length, 0, 'animals deleted from store');

      return Promise.all([
        app.model('person').delete(1),
        app.model('person').delete(2)
      ]);
    }).then(deletions => {
      assert.deepEqual(deletions, [ true, true ], 'both successes are true');
      assert.equal(app.adapter.store.person.length, 0, 'animals deleted from store');
      assert.end();
    });
  });

  t.test(`app.model.create animal with undefined owner`, assert => {
    app.model('animal').create({
      species: 'Cat',
      age: 5,
      owner: 1,
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'created animal is a resource');
      return animal.get('owner');
    }).then(owner => {
      assert.equal(owner, null, 'owner is null');
      assert.end();
    });
  });

  t.test(`resource.update and resource.delete`, assert => {
    app.model('animal').findResource(1).then(animal => {
      assert.ok(animal instanceof Resource, 'found animal is a resource');
      assert.equal(animal.attribute('species'), 'Cat', 'species attribute looks good');
      assert.equal(animal.attribute('age'), 5, 'species attribute looks good');

      return animal.update({ species: 'Lion' });
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'found animal is a resource');
      assert.equal(animal.attribute('species'), 'Lion', 'species attribute looks good');
      assert.equal(animal.attribute('age'), 5, 'species attribute looks good');

      return animal.delete();
    }).then(success => {
      assert.equal(success, true, 'animal resource deleted');
      assert.equal(app.adapter.store.animal.length, 0, 'no animals in the store');
      assert.end();
    });
  });

  t.test('app.model.create person', assert => {
    app.model('person').create({
      name: 'Eric Smith',
      age: 30,
      pets: [ 1 ],
    }).then(person => {
      assert.ok(person instanceof Resource, 'created person is a resource');

      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 0, 'no pets are there');

      return app.model('person').findResource(1);
    }).then(person => {
      return person.update({ pets: [] });
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 0, 'no pets are there');
      assert.deepEqual(app.adapter.store.person[0].pets, [], 'pets is an empty array in the store');

      return app.model('person').create({
        name: 'Peyton Manning',
        age: 39,
        pets: [],
      });
    }).then(person => {
      assert.end();
    });
  });

  t.test(`app.model.relationship (1)`, assert => {
    app.model('animal').create({
      species: 'Elephant',
      age: 22,
      owner: 1,
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'created animal is a resource');
      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'owner is a resource');
      assert.equal(owner.attribute('name'), 'Eric Smith', 'owner name attribute looks good');
      assert.equal(owner.attribute('age'), 30, 'owner age attribute looks good');

      return app.model('animal').findResource(1);
    }).then(animal => {
      const owner = animal.relationship('owner');
      assert.ok(owner instanceof Relationship, 'owner is a relationship');

      return owner.set([ 2 ]);
    }).catch(err => {
      assert.ok(err, 'there was an error setting the owner as an array');
      assert.end();
    });
  });

  t.test(`app.model.relationship (2)`, assert => {
    app.model('animal').findResource(1).then(animal => {
      return animal.relationship('owner').set(2);
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'updated animal is a resource');
      assert.equal(animal.attribute('species'), 'Elephant', 'species attribute looks good');

      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'owner is a resource');
      assert.equal(owner.attribute('name'), 'Peyton Manning', 'owner name attribute looks good');
      assert.equal(owner.attribute('age'), 39, 'owner age attribute looks good');

      return Promise.props({
        person: app.model('person').findResource(1),
        animal: app.model('animal').findResource(1),
      });
    }).then(({ person, animal }) => {
      assert.ok(person instanceof Resource, 'person is a resource');
      assert.ok(animal instanceof Resource, 'animal is a resource');

      return animal.set('owner', person);
    }).then(animal => {
      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'owner is a resource');
      assert.equal(owner.attribute('name'), 'Eric Smith', 'owner name attribute looks good');
      assert.equal(owner.attribute('age'), 30, 'owner age attribute looks good');

      return owner.delete();
    }).then(success => {
      assert.equal(success, true, 'owner deleted');

      return app.model('animal').findResource(1);
    }).then(animal => {
      return animal.get('owner');
    }).then(owner => {
      assert.equal(owner, null, 'the owner is deleted');
      assert.end();
    });
  });

  t.test(`app.model.relationship (3)`, assert => {
    app.model('person').findResource(2).then(person => {
      return person.set('pets', 1);
    }).catch(err => {
      assert.ok(err, 'cannot set a to-many relationship with a single value');
      assert.end();
    });
  });

  t.test(`app.model.relationship (4)`, assert => {
    app.model('person').findResource(2).then(person => {
      return person.set('pets', [ 1 ]);
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 1, 'there is one pet');

      return app.model('person').update(2, { pets: [] });
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 0, 'no more pets');

      return app.model('person').findResource(2);
    }).then(person => {
      return person.relationship('pets').append(1);
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 1, 'there is one pet');

      return app.model('person').update(2, { pets: [] });
    }).then(person => {
      assert.end();
    });
  });

  t.test(`app.model.relationship (5)`, assert => {
    Promise.props({
      person: app.model('person').findResource(2),
      animals: app.model('animal').find(),
    }).then(({ person, animals }) => {
      return person.set('pets', animals);
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 1, 'there is one pet');

      return app.model('person').update(2, { pets: [] });
    }).then(person => {
      assert.end();
    });
  });

  t.test(`app.model.relationship (6)`, assert => {
    Promise.props({
      person: app.model('person').findResource(2),
      animals: app.model('animal').find(),
    }).then(({ person, animals }) => {
      return person.relationship('pets').append(animals);
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 1, 'there is one pet');

      return app.model('person').update(2, { pets: [] });
    }).then(person => {
      app.adapter.store = {};
      assert.equal(Object.keys(app.adapter.store).length, 0, 'store is cleared');
      assert.end();
    });
  });

  t.test(`app.model.findRelated`, assert => {
    Promise.all([
      app.model('person').create({ name: 'Bob', age: 22, pets: [ 1, 2 ] }),
      app.model('person').create({ name: 'Jim', age: 26, pets: [ 2 ] }),
      app.model('animal').create({ species: 'Aardvark', age: 4, owner: 1 }),
      app.model('animal').create({ species: 'Zebra', age: 14, owner: 2 }),
    ]).then(records => {
      assert.equal(records.length, 4, 'four records were created');
      assert.equal(app.adapter.store.person.length, 2, 'two people created');
      assert.equal(app.adapter.store.animal.length, 2, 'two animals created');

      return app.model('person').findRelated(1, 'pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'related pets is a resource array');
      assert.equal(pets.resources.length, 2, 'two pets were found');

      return app.model('person').findRelated(1, 'pets', {
        filter: {
          age: { $gte: 14 },
        },
      });
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'related pets is a resource array');
      assert.equal(pets.resources.length, 1, 'only one pet was found this time');
      assert.equal(pets.resources[0].attribute('species'), 'Zebra', 'filtered animal is a Zebra');
      assert.end();
    });
  });

  t.test('finish', assert => {
    app.adapter.store = {};
    assert.equal(Object.keys(app.adapter.store).length, 0, 'store is cleared');
    resolve(true);
  });
});
<commit_msg>Add deep relationship test<commit_after>import Resource from '../../../dist/lib/model/resource';
import Relationship from '../../../dist/lib/model/relationship';
import ResourceArray from '../../../dist/lib/model/resource-array';
import Promise from 'bluebird';

export default (t, app) => new Promise(resolve => {
  t.test(`app.model('person').find`, assert => {
    app.model('person').find().then(people => {
      assert.ok(people instanceof ResourceArray, 'found people is a resource array');
      assert.equal(people.resources.length, 0, 'no people were found');
      assert.equal(people.count, 0, 'count property is zero');
      assert.end();
    });
  });

  t.test(`app.model.create`, assert => {
    app.model('person').create({
      name: 'Bobby Jones',
      age: 22,
      email: 'bjones@gmail.com',
    }).then(person => {
      assert.ok(person instanceof Resource, 'created person is a resource');
      assert.equal(person.get('name'), 'Bobby Jones', 'name attribute is good');
      assert.equal(person.get('email'), 'bjones@gmail.com', 'email attribute is good');
      assert.equal(person.get('age'), 22, 'age attribute is good');

      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'person pets is a resource array');
      assert.equal(pets.length, 0, 'person pets has zero length');

      return app.model('animal').create({
        species: 'Dog',
        age: 10,
        owner: 1,
      });
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'created animal is a resource');
      assert.equal(animal.get('species'), 'Dog', 'species attribute is good');
      assert.equal(animal.get('age'), 10, 'age attribute is good');

      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'animal owner is a resource');
      assert.equal(owner.get('name'), 'Bobby Jones', 'animal owner name attribute is good');
      assert.equal(owner.get('email'), 'bjones@gmail.com', 'animal owner email attribute is good');
      assert.equal(owner.get('age'), 22, 'animal owner age attribute is good');

      return app.model('person').create({
        name: 'Joe',
        age: 45,
        email: 'joe@gmail.com',
        pets: [ 1 ],
      });
    }).then(person => {
      assert.ok(person instanceof Resource, 'person is a resource');

      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'person pets is a resource array');
      assert.equal(pets.length, 1, 'person pets has length of 1');
      assert.end();
    });
  });

  t.test(`app.model.update`, assert => {
    app.model('animal').update(1, {
      age: 11,
      owner: 2,
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'updated animal is a resource');
      assert.equal(animal.get('species'), 'Dog', 'species attribute is good');
      assert.equal(animal.get('age'), 11, 'age attribute is good');

      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'animal owner is a resource');
      assert.equal(owner.get('name'), 'Joe', 'animal owner name attribute is good');
      assert.equal(owner.get('email'), 'joe@gmail.com', 'animal owner email attribute is good');
      assert.equal(owner.get('age'), 45, 'animal owner age attribute is good');

      return app.model('animal').update(1, {
        owner: null,
      });
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'updated animal is a resource');
      assert.equal(animal.get('species'), 'Dog', 'species attribute is good');
      assert.equal(animal.get('age'), 11, 'age attribute is good');

      return animal.get('owner');
    }).then(owner => {
      assert.equal(owner, null, 'owner is null');

      return app.model('person').update(2, {
        pets: [],
      });
    }).then(person => {
      assert.ok(person instanceof Resource, 'person is a resource');
      assert.equal(person.get('name'), 'Joe', 'person name attribute is good');
      assert.equal(person.get('email'), 'joe@gmail.com', 'person email attribute is good');
      assert.equal(person.get('age'), 45, 'person age attribute is good');

      return person.get('pets');
    }).then(pets => {
      assert.deepEqual(pets.resources, [], 'pets is an empty array');
      assert.end();
    });
  });

  t.test(`app.model.delete`, assert => {
    app.model('animal').delete(1).then(success => {
      assert.ok(success, 'success is true');
      assert.equal(app.adapter.store.animal.length, 0, 'animals deleted from store');

      return Promise.all([
        app.model('person').delete(1),
        app.model('person').delete(2)
      ]);
    }).then(deletions => {
      assert.deepEqual(deletions, [ true, true ], 'both successes are true');
      assert.equal(app.adapter.store.person.length, 0, 'animals deleted from store');
      assert.end();
    });
  });

  t.test(`app.model.create animal with undefined owner`, assert => {
    app.model('animal').create({
      species: 'Cat',
      age: 5,
      owner: 1,
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'created animal is a resource');
      return animal.get('owner');
    }).then(owner => {
      assert.equal(owner, null, 'owner is null');
      assert.end();
    });
  });

  t.test(`resource.update and resource.delete`, assert => {
    app.model('animal').findResource(1).then(animal => {
      assert.ok(animal instanceof Resource, 'found animal is a resource');
      assert.equal(animal.attribute('species'), 'Cat', 'species attribute looks good');
      assert.equal(animal.attribute('age'), 5, 'species attribute looks good');

      return animal.update({ species: 'Lion' });
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'found animal is a resource');
      assert.equal(animal.attribute('species'), 'Lion', 'species attribute looks good');
      assert.equal(animal.attribute('age'), 5, 'species attribute looks good');

      return animal.delete();
    }).then(success => {
      assert.equal(success, true, 'animal resource deleted');
      assert.equal(app.adapter.store.animal.length, 0, 'no animals in the store');
      assert.end();
    });
  });

  t.test('app.model.create person', assert => {
    app.model('person').create({
      name: 'Eric Smith',
      age: 30,
      pets: [ 1 ],
    }).then(person => {
      assert.ok(person instanceof Resource, 'created person is a resource');

      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 0, 'no pets are there');

      return app.model('person').findResource(1);
    }).then(person => {
      return person.update({ pets: [] });
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 0, 'no pets are there');
      assert.deepEqual(app.adapter.store.person[0].pets, [], 'pets is an empty array in the store');

      return app.model('person').create({
        name: 'Peyton Manning',
        age: 39,
        pets: [],
      });
    }).then(person => {
      assert.end();
    });
  });

  t.test(`app.model.relationship (1)`, assert => {
    app.model('animal').create({
      species: 'Elephant',
      age: 22,
      owner: 1,
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'created animal is a resource');
      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'owner is a resource');
      assert.equal(owner.attribute('name'), 'Eric Smith', 'owner name attribute looks good');
      assert.equal(owner.attribute('age'), 30, 'owner age attribute looks good');

      return app.model('animal').findResource(1);
    }).then(animal => {
      const owner = animal.relationship('owner');
      assert.ok(owner instanceof Relationship, 'owner is a relationship');

      return owner.set([ 2 ]);
    }).catch(err => {
      assert.ok(err, 'there was an error setting the owner as an array');
      assert.end();
    });
  });

  t.test(`app.model.relationship (2)`, assert => {
    app.model('animal').findResource(1).then(animal => {
      return animal.relationship('owner').set(2);
    }).then(animal => {
      assert.ok(animal instanceof Resource, 'updated animal is a resource');
      assert.equal(animal.attribute('species'), 'Elephant', 'species attribute looks good');

      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'owner is a resource');
      assert.equal(owner.attribute('name'), 'Peyton Manning', 'owner name attribute looks good');
      assert.equal(owner.attribute('age'), 39, 'owner age attribute looks good');

      return Promise.props({
        person: app.model('person').findResource(1),
        animal: app.model('animal').findResource(1),
      });
    }).then(({ person, animal }) => {
      assert.ok(person instanceof Resource, 'person is a resource');
      assert.ok(animal instanceof Resource, 'animal is a resource');

      return animal.set('owner', person);
    }).then(animal => {
      return animal.get('owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'owner is a resource');
      assert.equal(owner.attribute('name'), 'Eric Smith', 'owner name attribute looks good');
      assert.equal(owner.attribute('age'), 30, 'owner age attribute looks good');

      return owner.delete();
    }).then(success => {
      assert.equal(success, true, 'owner deleted');

      return app.model('animal').findResource(1);
    }).then(animal => {
      return animal.get('owner');
    }).then(owner => {
      assert.equal(owner, null, 'the owner is deleted');
      assert.end();
    });
  });

  t.test(`app.model.relationship (3)`, assert => {
    app.model('person').findResource(2).then(person => {
      return person.set('pets', 1);
    }).catch(err => {
      assert.ok(err, 'cannot set a to-many relationship with a single value');
      assert.end();
    });
  });

  t.test(`app.model.relationship (4)`, assert => {
    app.model('person').findResource(2).then(person => {
      return person.set('pets', [ 1 ]);
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 1, 'there is one pet');

      return app.model('person').update(2, { pets: [] });
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 0, 'no more pets');

      return app.model('person').findResource(2);
    }).then(person => {
      return person.relationship('pets').append(1);
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 1, 'there is one pet');

      return app.model('person').update(2, { pets: [] });
    }).then(person => {
      assert.end();
    });
  });

  t.test(`app.model.relationship (5)`, assert => {
    Promise.props({
      person: app.model('person').findResource(2),
      animals: app.model('animal').find(),
    }).then(({ person, animals }) => {
      return person.set('pets', animals);
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 1, 'there is one pet');

      return app.model('person').update(2, { pets: [] });
    }).then(person => {
      assert.end();
    });
  });

  t.test(`app.model.relationship (6)`, assert => {
    Promise.props({
      person: app.model('person').findResource(2),
      animals: app.model('animal').find(),
    }).then(({ person, animals }) => {
      return person.relationship('pets').append(animals);
    }).then(person => {
      return person.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'pets is a resource array');
      assert.equal(pets.resources.length, 1, 'there is one pet');

      return app.model('person').update(2, { pets: [] });
    }).then(person => {
      app.adapter.store = {};
      assert.equal(Object.keys(app.adapter.store).length, 0, 'store is cleared');
      assert.end();
    });
  });

  t.test(`app.model.findRelated`, assert => {
    Promise.all([
      app.model('person').create({ name: 'Bob', age: 22, pets: [ 1, 2 ] }),
      app.model('person').create({ name: 'Jim', age: 26, pets: [ 2 ] }),
      app.model('animal').create({ species: 'Aardvark', age: 4, owner: 1 }),
      app.model('animal').create({ species: 'Zebra', age: 14, owner: 2 }),
    ]).then(records => {
      assert.equal(records.length, 4, 'four records were created');
      assert.equal(app.adapter.store.person.length, 2, 'two people created');
      assert.equal(app.adapter.store.animal.length, 2, 'two animals created');

      return app.model('person').findRelated(1, 'pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'related pets is a resource array');
      assert.equal(pets.resources.length, 2, 'two pets were found');

      return app.model('person').findRelated(1, 'pets', {
        filter: {
          age: { $gte: 14 },
        },
      });
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'related pets is a resource array');
      assert.equal(pets.resources.length, 1, 'only one pet was found this time');
      assert.equal(pets.resources[0].attribute('species'), 'Zebra', 'filtered animal is a Zebra');

      return app.model('animal').findRelated(1, 'owner');
    }).then(owner => {
      assert.ok(owner instanceof Resource, 'related owner is a resource');
      assert.equal(owner.attribute('name'), 'Bob', 'related owner name attribute is good');
      assert.equal(owner.attribute('age'), 22, 'related owner name attribute is good');

      return owner.get('pets');
    }).then(pets => {
      assert.ok(pets instanceof ResourceArray, 'related pets is a resource array');
      assert.equal(pets.resources.length, 2, 'two pets was found this time');
      app.adapter.store = {};
      assert.end();
    });
  });

  t.test(`app.model.get`, assert => {
    Promise.all([
      app.model('person').create({ name: 'Bob', age: 22, pets: [ 1 ], company: 1 }),
      app.model('animal').create({ species: 'Snake', age: 10, owner: 1, habitats: [ 1 ] }),
      app.model('habitat').create({ name: 'Tropical', countries: [ 1 ] }),
      app.model('country').create({ name: 'Brazil' }),
      app.model('country').create({ name: 'USA' }),
      app.model('company').create({ name: 'Apple', industry: 'Computers', employees: [ 1 ], office: 1 }),
      app.model('building').create({ size: 10, location: 2 })
    ]).then(results => {
      assert.equal(results.length, 7, 'seven records were created');

      return app.model('person').findRelated(1, 'company');
    }).then(company => {
      assert.ok(company instanceof Resource, 'related company is a resource');
      assert.equal(company.attribute('name'), 'Apple', 'company name attribute is good');
      assert.equal(company.attribute('industry'), 'Computers', 'company computers attribute is good');

      return company.get('office');
    }).then(office => {
      assert.ok(office instanceof Resource, 'related office is a resource');
      assert.equal(office.attribute('size'), 10, 'company name attribute is good');

      return office.get('location');
    }).then(location => {
      assert.ok(location instanceof Resource, 'related location is a resource');
      assert.equal(location.attribute('name'), 'USA', 'location name attribute is good');
      assert.end();
    });
  });

  t.test('finish', assert => {
    app.adapter.store = {};
    assert.equal(Object.keys(app.adapter.store).length, 0, 'store is cleared');
    resolve(true);
  });
});
<|endoftext|>


<commit_before>/**
*
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import GoogleAuth from 'google-auth-library';
import uuidV4 from 'uuid/v4';

import {User, ADMIN_IDS, setNaiveLogin, naiveLoginAllowed} from './models';
import {Question} from '../quiz/models';
import {quiz, presentationListeners} from '../quiz/views';
import {longPollers} from '../long-pollers/views';
import promisify from '../promisify';
import {clientId, clientSecret, redirectOrigin} from '../settings';

const auth = new GoogleAuth();
auth.OAuth2.GOOGLE_OAUTH2_AUTH_BASE_URL_ = 'https://accounts.google.com/o/oauth2/v2/auth';
auth.OAuth2.GOOGLE_OAUTH2_TOKEN_URL_ = 'https://www.googleapis.com/oauth2/v4/token';

function getAuthClient() {
  return new auth.OAuth2(
    clientId,
    clientSecret,
    redirectOrigin + '/oauth2callback'
  );
}

async function authenticateUser(code) {
  const oauth2Client = getAuthClient();
  const tokens = await promisify(oauth2Client, 'getToken')(code);
  oauth2Client.setCredentials(tokens);
  const login = await promisify(oauth2Client, 'verifyIdToken')(tokens.id_token, clientId);
  const loginPayload = login.getPayload();

  const update = {
    googleId: loginPayload.sub,
    email: loginPayload.email
  };

  if (loginPayload.name) update.name = loginPayload.name;
  if (loginPayload.picture) {
    update.avatarUrl = loginPayload.picture.replace(/\/s96-c\/.*$/, '/');
  }

  return User.findOneAndUpdate({ googleId: update.googleId }, update, {
    upsert: true,
    new: true,
    setDefaultsOnInsert: true
  });
}

function logout(req) {
  return new Promise(r => req.session.destroy(r));
}

function requiresAdmin(req) {
  if (!req.user) {
    return "Login required";
  }

  if (!req.user.isAdmin()) {
    return "Admin only, soz";
  }

  return "";
}

export function generateAuthUrl({
  state = ''
}={}) {
  const oauth2Client = getAuthClient();
  return oauth2Client.generateAuthUrl({
    scope: ['openid', 'email', 'profile'],
    state
  });
}

export function userMiddleware(req, res, next) {
  if (!req.session.userId) {
    next();
    return;
  }

  User.findOne({googleId: req.session.userId}).then(user => {
    req.user = user;
    next();
  });
}

export function handleLogin(req, res) {
  authenticateUser(req.query.code).then(user => {
    req.session.userId = user.googleId;
    res.redirect(req.query.state);
  }).catch(err => {
    res.set('Content-Type', 'text/plain').send('Auth failed: ' + err.message);
  });
}

export function simpleUserObject(user) {
  return {
    googleId: user.googleId,
    name: user.name,
    email: user.email,
    avatarUrl: user.avatarUrl,
    optIntoLeaderboard: user.optIntoLeaderboard,
    score: user.score
  }
}

export function userJson(req, res) {
  if (!req.user) {
    res.json({
      user: null
    });
    return;
  }

  res.json({
    user: simpleUserObject(req.user)
  });
}

export function logoutRedirect(req, res) {
  logout(req).then(() => {
    res.redirect('/');
  });
}

export function logoutJson(req, res) {
  logout(req).then(() => {
    res.json({done: true});
  });
}

export function login(req, res) {
  res.redirect(generateAuthUrl({
    state: req.get('referrer')
  }));
}

export async function naiveLogin(req, res) {
  if (!naiveLoginAllowed()) {
    res.set('Content-Type', 'text/plain').send('Login failed: Naive login disabled.');
    return;
  }

  const name = String(req.body.name || '').slice(0, 25).trim();
  const id = 'naive-' + uuidV4();

  if (!name) {
    res.set('Content-Type', 'text/plain').send('Login failed: No name provided.');
    return;
  }

  await User.create({
    googleId: id,
    name,
    email: ''
  });

  req.session.userId = id;
  res.redirect('/');
}

export function updateUser(req, res) {
  // only dealing with optIntoLeaderboard for now
  if (!('optIntoLeaderboard' in req.body)) {
    res.json(simpleUserObject(req.user));
    return;
  }

  req.user.optIntoLeaderboard = !!req.body.optIntoLeaderboard;
  req.user.save().then(newUser => {
    res.json({
      user: simpleUserObject(newUser)
    });
  }).catch(err => {
    res.status(500).json({err: 'Update failed'})
    throw err;
  });
}

const adminIds = [
  '116237864387312784020' // Jake
];

export function requiresAdminHtml(req, res, next) {
  const err = requiresAdmin(req);

  if (err) {
    res.status(403).send(err);
    return;
  }

  next();
}

export function requiresAdminJson(req, res, next) {
  const err = requiresAdmin(req);

  if (err) {
    res.status(403).json({err});
    return;
  }

  next();
}

export function requiresLogin(req, res, next) {
  if (req.params.json) {
    return requiresLoginJson(req, res, next);
  }

  if (!req.user) {
    res.status(403).send("Not logged in");
    return;
  }

  next();
}

export function requiresLoginJson(req, res, next) {
  if (!req.user) {
    res.status(403).json({err: "Not logged in"});
    return;
  }

  next();
}

export function deleteUserAnswersJson(req, res) {
  User.update({}, {answers: [], score: 0}, {multi: true}).then(() => {
    res.json({});
  }).catch(err => {
    res.status(500).json({err});
  });
}

export function deleteUsersJson(req, res) {
  User.remove({}).then(() => {
    longPollers.broadcast({user: null});
    res.json({});
  }).catch(err => {
    res.status(500).json({err});
  });
}

export async function questionAnswerJson(req, res) {
  try {
    if (!quiz.activeQuestion) {
      res.json({err: "No question being asked"});
      return;
    }

    const question = await Question.findById(req.body.id);

    if (!question) {
      res.json({err: "Question not found"});
      return;
    }

    if (!question._id.equals(quiz.activeQuestion._id)) {
      res.json({err: "This question isn't currently being asked"});
      return;
    }

    if (!quiz.acceptingAnswers) {
      res.json({err: "Too late!"});
      return;
    }

    if (!Array.isArray(req.body.choices)) {
      res.json({err: "Choices is wrong type"});
      return;
    }

    // filter out bad answers and make unique
    const choices = [...new Set(
      req.body.choices.filter(choice => {
        // remove non-numbers
        if (typeof choice != 'number') return false;
        // remove out-of-range numbers
        if (choice < 0 || choice > quiz.activeQuestion.answers.length - 1) return false;
        return true;
      })
    )];

    if (!quiz.activeQuestion.multiple && choices.length != 1) {
      res.json({err: "Must provide one answer"});
      return;
    }

    const answerIndex = req.user.answers.findIndex(a => a.questionId.equals(question._id));
    quiz.cacheAnswers(req.user._id, choices);

    presentationListeners.broadcastThrottled({
      averages: quiz.getAverages()
    });

    if (answerIndex != -1) {
      req.user.answers[answerIndex].choices = choices;
    }
    else {
      req.user.answers.push({questionId: question._id, choices});
    }

    await req.user.save();
    res.json({});
  }
  catch (e) {
    res.status(500).json({err: "Unknown error"});
  }
}

function topUsers() {
  return User.find().limit(100).sort({score: -1}).then(users => {
    const userObjs = users.map(user => {
      const obj = simpleUserObject(user);
      obj.bannedFromLeaderboard = user.bannedFromLeaderboard;
      return obj;
    });
    return {users: userObjs};
  });
}

export function getTopUsersJson(req, res) {
  // this is for admins only
  topUsers().then(obj => {
    res.json(obj);
  }).catch(err => {
    res.status(500).json({err: err.message});
  });
}

export function setLeaderboardBanJson(req, res) {
  User.findOne({googleId: req.body.id}).then(user => {
    if (!user) {
      res.json({err: 'User not found'});
      return;
    }
    user.bannedFromLeaderboard = req.body.ban;
    return user.save();
  }).then(() => topUsers()).then(obj => {
    res.json(obj);
  }).catch(err => {
    res.status(500).json({err: err.message});
  });
}

export function allowNaiveLogin(req, res) {
  setNaiveLogin(true);
  res.json({naiveLoginAllowed: naiveLoginAllowed()})
}

export function disallowNaiveLogin(req, res) {
  setNaiveLogin(false);
  res.json({naiveLoginAllowed: naiveLoginAllowed()})
}<commit_msg>Google only<commit_after>/**
*
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import GoogleAuth from 'google-auth-library';
import uuidV4 from 'uuid/v4';

import {User, ADMIN_IDS, setNaiveLogin, naiveLoginAllowed} from './models';
import {Question} from '../quiz/models';
import {quiz, presentationListeners} from '../quiz/views';
import {longPollers} from '../long-pollers/views';
import promisify from '../promisify';
import {clientId, clientSecret, redirectOrigin} from '../settings';

const auth = new GoogleAuth();
auth.OAuth2.GOOGLE_OAUTH2_AUTH_BASE_URL_ = 'https://accounts.google.com/o/oauth2/v2/auth';
auth.OAuth2.GOOGLE_OAUTH2_TOKEN_URL_ = 'https://www.googleapis.com/oauth2/v4/token';

function getAuthClient() {
  return new auth.OAuth2(
    clientId,
    clientSecret,
    redirectOrigin + '/oauth2callback'
  );
}

async function authenticateUser(code) {
  const oauth2Client = getAuthClient();
  const tokens = await promisify(oauth2Client, 'getToken')(code);
  oauth2Client.setCredentials(tokens);
  const login = await promisify(oauth2Client, 'verifyIdToken')(tokens.id_token, clientId);
  const loginPayload = login.getPayload();

  const update = {
    googleId: loginPayload.sub,
    email: loginPayload.email
  };

  if (!(update.email.endsWith('@google.com') || ADMIN_IDS.includes(update.googleId))) {
    throw Error('Google employees only right now');
  }

  if (loginPayload.name) update.name = loginPayload.name;
  if (loginPayload.picture) {
    update.avatarUrl = loginPayload.picture.replace(/\/s96-c\/.*$/, '/');
  }

  return User.findOneAndUpdate({ googleId: update.googleId }, update, {
    upsert: true,
    new: true,
    setDefaultsOnInsert: true
  });
}

function logout(req) {
  return new Promise(r => req.session.destroy(r));
}

function requiresAdmin(req) {
  if (!req.user) {
    return "Login required";
  }

  if (!req.user.isAdmin()) {
    return "Admin only, soz";
  }

  return "";
}

export function generateAuthUrl({
  state = ''
}={}) {
  const oauth2Client = getAuthClient();
  return oauth2Client.generateAuthUrl({
    scope: ['openid', 'email', 'profile'],
    state
  });
}

export function userMiddleware(req, res, next) {
  if (!req.session.userId) {
    next();
    return;
  }

  User.findOne({googleId: req.session.userId}).then(user => {
    req.user = user;
    next();
  });
}

export function handleLogin(req, res) {
  authenticateUser(req.query.code).then(user => {
    req.session.userId = user.googleId;
    res.redirect(req.query.state);
  }).catch(err => {
    res.set('Content-Type', 'text/plain').send('Auth failed: ' + err.message);
  });
}

export function simpleUserObject(user) {
  return {
    googleId: user.googleId,
    name: user.name,
    email: user.email,
    avatarUrl: user.avatarUrl,
    optIntoLeaderboard: user.optIntoLeaderboard,
    score: user.score
  }
}

export function userJson(req, res) {
  if (!req.user) {
    res.json({
      user: null
    });
    return;
  }

  res.json({
    user: simpleUserObject(req.user)
  });
}

export function logoutRedirect(req, res) {
  logout(req).then(() => {
    res.redirect('/');
  });
}

export function logoutJson(req, res) {
  logout(req).then(() => {
    res.json({done: true});
  });
}

export function login(req, res) {
  res.redirect(generateAuthUrl({
    state: req.get('referrer')
  }));
}

export async function naiveLogin(req, res) {
  if (!naiveLoginAllowed()) {
    res.set('Content-Type', 'text/plain').send('Login failed: Naive login disabled.');
    return;
  }

  const name = String(req.body.name || '').slice(0, 25).trim();
  const id = 'naive-' + uuidV4();

  if (!name) {
    res.set('Content-Type', 'text/plain').send('Login failed: No name provided.');
    return;
  }

  await User.create({
    googleId: id,
    name,
    email: ''
  });

  req.session.userId = id;
  res.redirect('/');
}

export function updateUser(req, res) {
  // only dealing with optIntoLeaderboard for now
  if (!('optIntoLeaderboard' in req.body)) {
    res.json(simpleUserObject(req.user));
    return;
  }

  req.user.optIntoLeaderboard = !!req.body.optIntoLeaderboard;
  req.user.save().then(newUser => {
    res.json({
      user: simpleUserObject(newUser)
    });
  }).catch(err => {
    res.status(500).json({err: 'Update failed'})
    throw err;
  });
}

const adminIds = [
  '116237864387312784020' // Jake
];

export function requiresAdminHtml(req, res, next) {
  const err = requiresAdmin(req);

  if (err) {
    res.status(403).send(err);
    return;
  }

  next();
}

export function requiresAdminJson(req, res, next) {
  const err = requiresAdmin(req);

  if (err) {
    res.status(403).json({err});
    return;
  }

  next();
}

export function requiresLogin(req, res, next) {
  if (req.params.json) {
    return requiresLoginJson(req, res, next);
  }

  if (!req.user) {
    res.status(403).send("Not logged in");
    return;
  }

  next();
}

export function requiresLoginJson(req, res, next) {
  if (!req.user) {
    res.status(403).json({err: "Not logged in"});
    return;
  }

  next();
}

export function deleteUserAnswersJson(req, res) {
  User.update({}, {answers: [], score: 0}, {multi: true}).then(() => {
    res.json({});
  }).catch(err => {
    res.status(500).json({err});
  });
}

export function deleteUsersJson(req, res) {
  User.remove({}).then(() => {
    longPollers.broadcast({user: null});
    res.json({});
  }).catch(err => {
    res.status(500).json({err});
  });
}

export async function questionAnswerJson(req, res) {
  try {
    if (!quiz.activeQuestion) {
      res.json({err: "No question being asked"});
      return;
    }

    const question = await Question.findById(req.body.id);

    if (!question) {
      res.json({err: "Question not found"});
      return;
    }

    if (!question._id.equals(quiz.activeQuestion._id)) {
      res.json({err: "This question isn't currently being asked"});
      return;
    }

    if (!quiz.acceptingAnswers) {
      res.json({err: "Too late!"});
      return;
    }

    if (!Array.isArray(req.body.choices)) {
      res.json({err: "Choices is wrong type"});
      return;
    }

    // filter out bad answers and make unique
    const choices = [...new Set(
      req.body.choices.filter(choice => {
        // remove non-numbers
        if (typeof choice != 'number') return false;
        // remove out-of-range numbers
        if (choice < 0 || choice > quiz.activeQuestion.answers.length - 1) return false;
        return true;
      })
    )];

    if (!quiz.activeQuestion.multiple && choices.length != 1) {
      res.json({err: "Must provide one answer"});
      return;
    }

    const answerIndex = req.user.answers.findIndex(a => a.questionId.equals(question._id));
    quiz.cacheAnswers(req.user._id, choices);

    presentationListeners.broadcastThrottled({
      averages: quiz.getAverages()
    });

    if (answerIndex != -1) {
      req.user.answers[answerIndex].choices = choices;
    }
    else {
      req.user.answers.push({questionId: question._id, choices});
    }

    await req.user.save();
    res.json({});
  }
  catch (e) {
    res.status(500).json({err: "Unknown error"});
  }
}

function topUsers() {
  return User.find().limit(100).sort({score: -1}).then(users => {
    const userObjs = users.map(user => {
      const obj = simpleUserObject(user);
      obj.bannedFromLeaderboard = user.bannedFromLeaderboard;
      return obj;
    });
    return {users: userObjs};
  });
}

export function getTopUsersJson(req, res) {
  // this is for admins only
  topUsers().then(obj => {
    res.json(obj);
  }).catch(err => {
    res.status(500).json({err: err.message});
  });
}

export function setLeaderboardBanJson(req, res) {
  User.findOne({googleId: req.body.id}).then(user => {
    if (!user) {
      res.json({err: 'User not found'});
      return;
    }
    user.bannedFromLeaderboard = req.body.ban;
    return user.save();
  }).then(() => topUsers()).then(obj => {
    res.json(obj);
  }).catch(err => {
    res.status(500).json({err: err.message});
  });
}

export function allowNaiveLogin(req, res) {
  setNaiveLogin(true);
  res.json({naiveLoginAllowed: naiveLoginAllowed()})
}

export function disallowNaiveLogin(req, res) {
  setNaiveLogin(false);
  res.json({naiveLoginAllowed: naiveLoginAllowed()})
}<|endoftext|>