DanielHreben/sequelize-transparent-cache

View on GitHub
packages/sequelize-transparent-cache/src/methods/__test__/class.test.js

Summary

Maintainability
A
0 mins
Test Coverage
const sequelize = require('./sequelize')

const { User, Article, Comment, Group } = sequelize.models
const cacheStore = User.cache().client().store

beforeAll(() => sequelize.sync())

describe('Class methods', () => {
  test('Cache is empty on start', () => {
    expect(cacheStore).toEqual({})
  })
  test('Create', async () => {
    const user = await User.cache().create({
      id: 1,
      name: 'Daniel'
    })

    const article = await Article.cache().create({
      uuid: '2086c06e-9dd9-4ee3-84b9-9e415dfd9c4c',
      title: 'New article'
    })
    await user.setArticles([article])
    await user.cache().save()

    const comment = await Comment.cache().create({
      userId: user.id,
      articleUuid: article.uuid,
      body: 'New comment'
    })

    const group = await Group.cache().create({
      id: 1,
      name: 'Group of wonderful people'
    })

    await group.setGroupUsers([user])
    await group.cache().save()

    // User with default primary key cached after create
    expect(cacheStore.User[1]).toEqual(
      JSON.stringify(user)
    )

    // Entity with custom primary key cached after create
    expect(cacheStore.Article[article.uuid]).toEqual(
      JSON.stringify(article)
    )

    // Entity with composite primary keys cached after create
    expect(cacheStore.Comment[`${comment.userId},${comment.articleUuid}`]).toEqual(
      JSON.stringify(comment)
    )

    // Cached user with primary key correctly loaded
    expect((await User.cache().findByPk(1)).get()).toEqual(
      user.get()
    )

    // Cached entity correctly loaded using custom primary key
    expect((await Article.cache().findByPk(article.uuid)).get()).toEqual(
      article.get()
    )

    expect((await Group.cache().findByPk(1)).get()).toEqual(
      group.get()
    )
  })

  test('Upsert', async () => {
    const user = await User.cache().findByPk(1)

    await User.cache().upsert({
      id: 1,
      name: 'Ivan'
    })

    // Timestamps synced after upsert
    expect((await User.cache().findByPk(1)).get()).toEqual(
      (await User.findByPk(1)).get()
    )

    await user.cache().reload()

    // User name was updated
    expect(user.name).toBe('Ivan')

    // User cached after upsert
    expect(cacheStore.User[1]).toEqual(
      JSON.stringify(user) // TODO fix loading superfluous data
    )

    const group = await Group.cache().findByPk(1)

    await Group.cache().upsert({
      id: 1,
      name: 'Group of best people'
    })

    expect((await Group.cache().findByPk(1)).get()).toEqual(
      (await Group.findByPk(1)).get()
    )

    await group.cache().reload()
    expect(group.name).toBe('Group of best people')
  })

  test('findByPk', async () => {
    expect(await User.cache().findByPk(2)).toBeNull() // Cache miss not causing any problem

    delete cacheStore.User[1]
    // Deleted so first find goes directly to DB & and second one retrieves from cache with association

    const getQuery = async () => {
      const user = await User.cache().findByPk(1, { include: [{ model: Article, as: 'Articles' }] })
      const articles = user.Articles[0].get()
      return articles
    }

    // Retrieved user with Article association
    expect(await getQuery()).toEqual(
      await getQuery()
    )

    const getGroupQuery = async () => {
      const group = await Group.cache().findByPk(1, { include: [{ model: User, as: 'groupUsers' }] })
      return group.get().groupUsers[0].get().name
    }
    expect(await getGroupQuery()).toBe(
      await getGroupQuery()
    )
  })

  test('cache -> findAll', async () => {
    const missingUsers = await User.cache('missingKey1').findAll({ where: { name: 'Not existent' } })

    // Cache miss not causing any problem
    expect(missingUsers).toEqual(
      []
    )

    const key = 'IvanUserCacheKey1'
    const getQuery = () => ({
      where: { name: 'Ivan' },
      include: [{ model: Article, as: 'Articles' }]
    })

    const [cacheMiss] = await User.cache(key).findAll(getQuery())
    const [cacheHit] = await User.cache(key).findAll(getQuery())
    const [dbValue] = await User.findAll(getQuery())

    // Returned value is the same, not matter if cache hit or miss
    expect(cacheMiss.get().Articles[0].get()).toEqual(
      cacheHit.get().Articles[0].get()
    )

    // 'Returned value is the same, as in db'
    expect(cacheHit.get().Articles[0].get()).toEqual(
      dbValue.get().Articles[0].get()
    )
  })

  test('cache -> findOne', async () => {
    const missingUser = await User.cache('MissingKey2').findOne({ where: { name: 'Not existent' } })

    expect(missingUser).toBeNull() // Cache miss not causing any problem

    const key = 'IvanUserCacheKey2'
    const getQuery = () => ({
      where: { name: 'Ivan' },
      include: [{ model: Article, as: 'Articles' }]
    })

    const cacheMiss = await User.cache(key).findOne(getQuery())
    const cacheHit = await User.cache(key).findOne(getQuery())
    const dbValue = await User.findOne(getQuery())

    // Returned value is the same, not matter if cache hit or miss
    expect(cacheMiss.get().Articles[0].get().uuid).toBe(
      cacheHit.get().Articles[0].get().uuid
    )

    // Returned value is the same, as in db
    expect(cacheHit.get().Articles[0].get().uuid).toBe(
      dbValue.get().Articles[0].get().uuid
    )
  })

  test('cache -> cache store', async () => {
    const key = 'ClearStoreUserCacheKey'

    const cachedUser = await User.cache(key).findOne({ where: { name: 'Ivan' } })
    const userFromDb = await User.findOne({ where: { name: 'Ivan' } })

    // User cached after find and present in key using cache store
    expect(userFromDb.get()).toEqual(
      cachedUser.get()
    )
  })

  test('cache -> clear', async () => {
    const key = 'ClearUserCacheKey'

    const cachedUser = await User.cache(key).findOne({ where: { name: 'Ivan' } })
    const userFromDb = await User.findOne({ where: { name: 'Ivan' } })

    // User cached after find and present in key
    expect(userFromDb.get()).toEqual(
      cachedUser.get()
    )

    await User.cache(key).clear()
    expect(cacheStore.User[key]).toBeUndefined() // User was deleted from cache
  })
})

describe('Recursive include tests', () => {
  test('Nested include depth == 1', async () => {
    // get all users of a group
    const group = await Group.cache().create({
      id: 123,
      name: 'Crazy Bloggers1'
    })

    const user1 = await User.cache().create({
      id: 123,
      name: 'Bob1'
    })

    const user2 = await User.cache().create({
      id: 124,
      name: 'Alice1'
    })

    await group.setGroupUsers([user1, user2])
    await group.cache().save()

    // From DB
    const groupFromDB = await Group.cache('CrazyBloggers12').findAll({
      include: [{
        model: User,
        as: 'groupUsers'
      }],
      where: { id: 123 }
    })

    // From cache
    const cachedGroup = await Group.cache('CrazyBloggers12').findAll({
      include: [{
        model: User,
        as: 'groupUsers'
      }],
      where: { id: 123 }
    })
    // comparing length at depth == 0
    expect(cachedGroup).toHaveLength(groupFromDB.length)
    // comparing dataValues at depth == 0
    expect(cachedGroup[0].get().id).toEqual(groupFromDB[0].get().id)
    expect(cachedGroup[0].get().name).toEqual(groupFromDB[0].get().name)
    // comparing length at depth == 1
    expect(cachedGroup[0].groupUsers).toHaveLength(groupFromDB[0].groupUsers.length)
    // comparing dataValues at depth == 1
    expect(cachedGroup[0].groupUsers[0].get().id).toEqual(groupFromDB[0].groupUsers[0].get().id)
    expect(cachedGroup[0].groupUsers[0].get().name).toEqual(groupFromDB[0].groupUsers[0].get().name)
  })

  test('Nested include depth == 2', async () => {
    // get all the aricles written by all users in a group
    const group = await Group.cache().create({
      id: 10,
      name: 'Crazy Bloggers1'
    })

    const user1 = await User.cache().create({
      id: 10,
      name: 'Bob'
    })

    const article = await Article.cache().create({
      uuid: '2086c06e-9dd9-4ee3-84b9-9e415dfd9c4f',
      title: 'New article'
    })
    await user1.setArticles([article])
    await user1.cache().save()

    const user2 = await User.cache().create({
      id: 11,
      name: 'Alice'
    })

    await group.setGroupUsers([user1, user2])
    await group.cache().save()

    // Crazy Bloggers has Bob and Alice.
    // Bob has written one article. Alice has written none

    // From DB
    const groupFromDB = await Group.cache('CrazyBloggers1').findAll({
      include: [{
        model: User,
        as: 'groupUsers',
        include: [{
          model: Article,
          as: 'Articles'
        }]
      }],
      where: { id: 10 }
    })

    // From cache
    const cachedGroup = await Group.cache('CrazyBloggers1').findAll({
      include: [{
        model: User,
        as: 'groupUsers',
        include: [{
          model: Article,
          as: 'Articles'
        }]
      }],
      where: { id: 10 }
    })

    // comparing length at depth == 0
    expect(cachedGroup).toHaveLength(groupFromDB.length)
    // comparing dataValues at depth == 0 i.e. "Crazy Bloggers"
    expect(cachedGroup[0].get().id).toEqual(groupFromDB[0].get().id)
    expect(cachedGroup[0].get().name).toEqual(groupFromDB[0].get().name)
    // comparing length at depth == 1
    expect(cachedGroup[0].groupUsers).toHaveLength(groupFromDB[0].groupUsers.length)
    // comparing dataValues at depth == 1
    // Bob is present
    expect(cachedGroup[0].groupUsers[0].get().id).toEqual(groupFromDB[0].groupUsers[0].get().id)
    expect(cachedGroup[0].groupUsers[0].get().name).toEqual(groupFromDB[0].groupUsers[0].get().name)
    // Alice is present
    expect(cachedGroup[0].groupUsers[1].get().id).toEqual(groupFromDB[0].groupUsers[1].get().id)
    expect(cachedGroup[0].groupUsers[1].get().name).toEqual(groupFromDB[0].groupUsers[1].get().name)
    // Bob has an article; depth == 2
    expect(cachedGroup[0].groupUsers[0].Articles).toHaveLength(groupFromDB[0].groupUsers[0].Articles.length)
    expect(cachedGroup[0].groupUsers[0].Articles[0].get().id).toEqual(groupFromDB[0].groupUsers[0].Articles[0].get().id)
    expect(cachedGroup[0].groupUsers[0].Articles[0].get().name).toEqual(groupFromDB[0].groupUsers[0].Articles[0].get().name)
    // Alice has no article; depth == 2
    expect(cachedGroup[0].groupUsers[1].Articles).toEqual([])
  })
})