@prisma/client#EventType TypeScript Examples

The following examples show how to use @prisma/client#EventType. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: deposits.upsert.service.spec.ts    From ironfish-api with Mozilla Public License 2.0 4 votes vote down vote up
describe('DepositsUpsertService', () => {
  let app: INestApplication;
  let depositHeadsService: DepositHeadsService;
  let depositsUpsertService: DepositsUpsertService;
  let graphileWorkerService: GraphileWorkerService;
  let prisma: PrismaService;
  let usersService: UsersService;

  let user1: User;
  let user2: User;
  let transaction1: DepositTransactionDto;
  let transaction2: DepositTransactionDto;

  beforeAll(async () => {
    app = await bootstrapTestApp();
    depositHeadsService = app.get(DepositHeadsService);
    depositsUpsertService = app.get(DepositsUpsertService);
    graphileWorkerService = app.get(GraphileWorkerService);
    prisma = app.get(PrismaService);
    usersService = app.get(UsersService);
    await app.init();

    user1 = await usersService.create({
      email: faker.internet.email(),
      graffiti: 'user1',
      country_code: faker.address.countryCode(),
    });

    user2 = await usersService.create({
      email: faker.internet.email(),
      graffiti: 'user2',
      country_code: faker.address.countryCode(),
    });

    transaction1 = transaction(
      [...notes([1, 2], user1.graffiti), ...notes([0.1, 3], user2.graffiti)],
      'transaction1Hash',
    );

    transaction2 = transaction(
      [...notes([0.05], user1.graffiti), ...notes([1], user2.graffiti)],
      'transaction2Hash',
    );
  });

  afterAll(async () => {
    await app.close();
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('bulkUpsert', () => {
    it('queues upsert deposit jobs for the payloads', async () => {
      const addJob = jest
        .spyOn(graphileWorkerService, 'addJob')
        .mockImplementation(jest.fn());

      const payload = {
        operations: [
          depositOperation(
            [transaction1],
            BlockOperation.CONNECTED,
            'block1Hash',
          ),
          depositOperation(
            [transaction2],
            BlockOperation.CONNECTED,
            'block2Hash',
          ),
        ],
      };

      await depositsUpsertService.bulkUpsert(payload.operations);

      expect(addJob).toHaveBeenCalledTimes(payload.operations.length);
      assert.ok(addJob.mock.calls);
      for (let i = 0; i < payload.operations.length; i++) {
        expect(addJob.mock.calls[i][0]).toBe(
          GraphileWorkerPattern.UPSERT_DEPOSIT,
        );
        expect(addJob.mock.calls[i][1]).toEqual(payload.operations[i]);
      }
    });
  });

  describe('upsert', () => {
    it('upserts new deposits and events', async () => {
      const payload = depositOperation(
        [transaction1, transaction2],
        BlockOperation.CONNECTED,
        'block1Hash',
      );

      await depositsUpsertService.upsert(payload);

      const user1Events = await prisma.event.findMany({
        where: {
          user_id: user1.id,
          type: EventType.SEND_TRANSACTION,
        },
      });

      const user2Events = await prisma.event.findMany({
        where: {
          user_id: user2.id,
          type: EventType.SEND_TRANSACTION,
        },
      });

      expect(user1Events).toHaveLength(1);
      expect(user2Events).toHaveLength(2);

      const user1Deposits = await prisma.deposit.findMany({
        where: {
          graffiti: user1.graffiti,
        },
      });

      const user2Deposits = await prisma.deposit.findMany({
        where: {
          graffiti: user2.graffiti,
        },
      });

      expect(user1Deposits).toHaveLength(2);
      expect(user2Deposits).toHaveLength(2);

      expect(user1Events[0].deposit_id).toEqual(user1Deposits[0].id);
      expect(user2Events[0].deposit_id).toEqual(user2Deposits[0].id);
      expect(user2Events[1].deposit_id).toEqual(user2Deposits[1].id);
    });

    describe('on DISCONNECTED operations', () => {
      it('removes events', async () => {
        const payload = depositOperation(
          [transaction2],
          BlockOperation.DISCONNECTED,
          'block1Hash',
        );

        await depositsUpsertService.upsert(payload);

        const user2Events = await prisma.event.findMany({
          where: {
            user_id: user2.id,
            type: EventType.SEND_TRANSACTION,
          },
        });

        const user1Deposits = await prisma.deposit.findMany({
          where: {
            graffiti: user1.graffiti,
          },
        });

        const user2Deposits = await prisma.deposit.findMany({
          where: {
            graffiti: user2.graffiti,
          },
        });

        expect(user2Events[0].points).toBe(1);
        expect(user2Events[1].points).toBe(0);
        expect(user2Events[1].deposit_id).toEqual(user2Deposits[1].id);
        expect(user2Deposits[1].amount).toEqual(1 * ORE_TO_IRON);

        expect(user1Deposits).toHaveLength(2);
        expect(user2Deposits).toHaveLength(2);

        expect(user1Deposits[1].main).toBe(false);
        expect(user2Deposits[1].main).toBe(false);
      });
    });

    it('updates the deposit head', async () => {
      const updateHead = jest
        .spyOn(depositHeadsService, 'upsert')
        .mockImplementation(jest.fn());

      const operation = depositOperation(
        [transaction1],
        BlockOperation.CONNECTED,
        'block1Hash',
      );
      await depositsUpsertService.upsert(operation);

      assert.ok(updateHead.mock.calls);
      expect(updateHead.mock.calls[0][0]).toBe(operation.block.hash);
    });

    describe('on FORK operations', () => {
      it('does not delete events on FORK operations', async () => {
        const transaction3 = transaction(
          [...notes([0.1], user2.graffiti)],
          'transaction3Hash',
        );

        const payload = depositOperation(
          [transaction3],
          BlockOperation.CONNECTED,
          'block3Hash',
        );

        await depositsUpsertService.upsert(payload);

        const user2EventsBefore = await prisma.event.findMany({
          where: {
            user_id: user2.id,
            type: EventType.SEND_TRANSACTION,
          },
        });

        const forkPayload = depositOperation(
          [transaction3],
          BlockOperation.FORK,
          'block3Hash',
        );

        await depositsUpsertService.upsert(forkPayload);

        const user2EventsAfter = await prisma.event.findMany({
          where: {
            user_id: user2.id,
            type: EventType.SEND_TRANSACTION,
          },
        });

        expect(user2EventsBefore).toEqual(user2EventsAfter);
      });
    });
  });

  const notes = (amounts: number[], graffiti: string) => {
    return amounts.map((amount) => {
      return { memo: graffiti, amount: amount * ORE_TO_IRON };
    });
  };

  const transaction = (notes: UpsertDepositsNoteDto[], hash?: string) => {
    return {
      hash: hash || uuid(),
      notes,
    };
  };

  const depositOperation = (
    transactions: DepositTransactionDto[],
    type: BlockOperation,
    hash?: string,
    previousBlockHash?: string,
    sequence?: number,
  ): UpsertDepositsOperationDto => {
    return {
      type,
      block: {
        hash: hash || uuid(),
        timestamp: new Date(),
        sequence: sequence || 0,
        previousBlockHash: previousBlockHash || uuid(),
      },
      transactions,
    };
  };
});
Example #2
Source File: deposits.upsert.service.ts    From ironfish-api with Mozilla Public License 2.0 4 votes vote down vote up
async upsert(operation: UpsertDepositsOperationDto): Promise<Deposit[]> {
    const networkVersion = this.config.get<number>('NETWORK_VERSION');
    const deposits = new Array<Deposit>();

    for (const transaction of operation.transactions) {
      const shouldUpsertDeposit =
        operation.type === BlockOperation.CONNECTED ||
        operation.type === BlockOperation.DISCONNECTED;
      if (!shouldUpsertDeposit) {
        continue;
      }

      await this.prisma.$transaction(async (prisma) => {
        const amounts = new Map<string, number>();

        for (const deposit of transaction.notes) {
          const amount = amounts.get(deposit.memo) ?? 0;
          amounts.set(deposit.memo, amount + deposit.amount);
        }

        for (const [graffiti, amount] of amounts) {
          const depositParams = {
            transaction_hash: standardizeHash(transaction.hash),
            block_hash: standardizeHash(operation.block.hash),
            block_sequence: operation.block.sequence,
            network_version: networkVersion,
            graffiti,
            main: operation.type === BlockOperation.CONNECTED,
            amount,
          };

          const deposit = await prisma.deposit.upsert({
            create: depositParams,
            update: depositParams,
            where: {
              uq_deposits_on_transaction_hash_and_graffiti: {
                transaction_hash: depositParams.transaction_hash,
                graffiti: depositParams.graffiti,
              },
            },
          });
          deposits.push(deposit);

          if (!deposit.main) {
            const event = await prisma.event.findUnique({
              where: {
                deposit_id: deposit.id,
              },
            });
            if (event) {
              await this.eventsService.deleteWithClient(event, prisma);
            }
          }

          if (deposit.main && deposit.amount >= SEND_TRANSACTION_LIMIT_ORE) {
            const user = await this.usersService.findByGraffiti(
              deposit.graffiti,
              prisma,
            );

            if (user) {
              await this.eventsService.createWithClient(
                {
                  occurredAt: operation.block.timestamp,
                  type: EventType.SEND_TRANSACTION,
                  userId: user.id,
                  deposit,
                },
                prisma,
              );
            }
          }
        }

        const headHash =
          operation.type === BlockOperation.CONNECTED
            ? operation.block.hash
            : operation.block.previousBlockHash;
        await this.depositHeadsService.upsert(headHash, prisma);
      });
    }

    return deposits;
  }
Example #3
Source File: users.controller.spec.ts    From ironfish-api with Mozilla Public License 2.0 4 votes vote down vote up
describe('UsersController', () => {
  let app: INestApplication;
  let magicLinkService: MagicLinkService;
  let usersService: UsersService;
  let eventsService: EventsService;
  let eventsJobsController: EventsJobsController;

  beforeAll(async () => {
    app = await bootstrapTestApp();
    magicLinkService = app.get(MagicLinkService);
    usersService = app.get(UsersService);
    eventsService = app.get(EventsService);
    eventsJobsController = app.get(EventsJobsController);

    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  describe('GET /users/:id', () => {
    describe('with a valid id', () => {
      it('returns the user', async () => {
        const user = await usersService.create({
          email: faker.internet.email(),
          graffiti: uuid(),
          country_code: faker.address.countryCode(),
        });
        const { body } = await request(app.getHttpServer())
          .get(`/users/${user.id}`)
          .expect(HttpStatus.OK);

        expect(body).toMatchObject({
          id: user.id,
          graffiti: user.graffiti,
          total_points: expect.any(Number),
          created_at: user.created_at.toISOString(),
          rank: await usersService.getRank(user),
        });
      });
    });

    describe('with a missing id', () => {
      it('returns a 404', async () => {
        await request(app.getHttpServer())
          .get('/users/12345')
          .expect(HttpStatus.NOT_FOUND);
      });
    });
  });

  describe('GET /users/find', () => {
    describe('with rank requested', () => {
      describe('with a valid graffiti', () => {
        it('returns the user', async () => {
          const graffiti = uuid();
          const user = await usersService.create({
            email: faker.internet.email(),
            graffiti,
            country_code: faker.address.countryCode(),
          });
          const { body } = await request(app.getHttpServer())
            .get(`/users/find`)
            .query({ graffiti, with_rank: true })
            .expect(HttpStatus.OK);

          expect(body).toMatchObject({
            id: user.id,
            graffiti: user.graffiti,
            total_points: expect.any(Number),
            created_at: user.created_at.toISOString(),
            rank: await usersService.getRank(user),
          });
        });
      });
    });

    describe('with rank not requested', () => {
      describe('with a valid graffiti', () => {
        it('returns the user', async () => {
          const graffiti = uuid();
          const user = await usersService.create({
            email: faker.internet.email(),
            graffiti,
            country_code: faker.address.countryCode(),
          });
          const { body } = await request(app.getHttpServer())
            .get(`/users/find`)
            .query({ graffiti })
            .expect(HttpStatus.OK);

          expect(body).not.toHaveProperty('rank');
          expect(body).toMatchObject({
            id: user.id,
            graffiti: user.graffiti,
            total_points: expect.any(Number),
            created_at: user.created_at.toISOString(),
          });
        });
      });
    });

    describe('with a missing graffiti', () => {
      it('returns a 404', async () => {
        await request(app.getHttpServer())
          .get('/users/find?graffiti=12345')
          .expect(HttpStatus.NOT_FOUND);
      });
    });
  });

  describe('GET /users/:id/metrics', () => {
    describe('with start but no end', () => {
      it('returns a 422', async () => {
        const { body } = await request(app.getHttpServer())
          .get('/users/123/metrics')
          .query({
            start: new Date().toISOString(),
            granularity: MetricsGranularity.TOTAL,
          })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with end but no start', () => {
      it('returns a 422', async () => {
        const { body } = await request(app.getHttpServer())
          .get('/users/123/metrics')
          .query({
            end: new Date().toISOString(),
            granularity: MetricsGranularity.TOTAL,
          })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with a missing granularity', () => {
      it('returns a 422', async () => {
        const { body } = await request(app.getHttpServer())
          .get('/users/123/metrics')
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with invalid granularity', () => {
      it('returns a 422', async () => {
        const { body } = await request(app.getHttpServer())
          .get('/users/123/metrics')
          .query({ granularity: MetricsGranularity.DAY })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with a time range for a LIFETIME request', () => {
      it('returns a 422', async () => {
        const { body } = await request(app.getHttpServer())
          .get('/users/123/metrics')
          .query({
            start: new Date().toISOString(),
            end: new Date().toISOString(),
            granularity: MetricsGranularity.LIFETIME,
          })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with start after end', () => {
      it('returns a 422', async () => {
        const start = new Date().toISOString();
        const end = new Date(Date.now() - 1).toISOString();
        const { body } = await request(app.getHttpServer())
          .get('/users/123/metrics')
          .query({
            start,
            end,
            granularity: MetricsGranularity.TOTAL,
          })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with a time range longer than the supported range', () => {
      it('returns a 422', async () => {
        const start = '2021-06-01T00:00:00.000Z';
        const end = '2021-08-01T00:00:00.000Z';
        const { body } = await request(app.getHttpServer())
          .get('/users/123/metrics')
          .query({
            start,
            end,
            granularity: MetricsGranularity.TOTAL,
          })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with a TOTAL request and no time range', () => {
      it('returns a 422', async () => {
        const user = await usersService.create({
          email: faker.internet.email(),
          graffiti: uuid(),
          country_code: faker.address.countryCode('alpha-3'),
        });

        const { body } = await request(app.getHttpServer())
          .get(`/users/${user.id}/metrics`)
          .query({
            granularity: MetricsGranularity.TOTAL,
          })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with a missing user', () => {
      it('returns a 404', async () => {
        const start = new Date(Date.now() - 1).toISOString();
        const end = new Date().toISOString();
        await request(app.getHttpServer())
          .get('/users/123456789/metrics')
          .query({
            start,
            end,
            granularity: MetricsGranularity.TOTAL,
          })
          .expect(HttpStatus.NOT_FOUND);
      });
    });

    describe('with a valid lifetime request', () => {
      it('returns the lifetime metrics for the user', async () => {
        const user = await usersService.create({
          email: faker.internet.email(),
          graffiti: uuid(),
          country_code: faker.address.countryCode('alpha-3'),
        });

        await eventsService.create({
          userId: user.id,
          type: EventType.BUG_CAUGHT,
          points: 100,
        });

        await eventsService.create({
          userId: user.id,
          type: EventType.PULL_REQUEST_MERGED,
          points: 500,
        });

        await eventsJobsController.updateLatestPoints({
          userId: user.id,
          type: EventType.PULL_REQUEST_MERGED,
        });

        await eventsJobsController.updateLatestPoints({
          userId: user.id,
          type: EventType.BUG_CAUGHT,
        });

        const { body } = await request(app.getHttpServer())
          .get(`/users/${user.id}/metrics`)
          .query({
            granularity: MetricsGranularity.LIFETIME,
          });

        expect(body).toMatchObject({
          user_id: user.id,
          granularity: MetricsGranularity.LIFETIME,
          points: expect.any(Number),
          node_uptime: {
            total_hours: 0,
            last_checked_in: null,
          },
          pools: {
            main: {
              rank: expect.any(Number),
              count: expect.any(Number),
              points: 100,
            },
            code: {
              rank: expect.any(Number),
              count: expect.any(Number),
              points: 500,
            },
          },
          metrics: {
            blocks_mined: {
              count: 0,
              points: 0,
            },
            bugs_caught: {
              count: 1,
              points: 100,
            },
            community_contributions: {
              count: 0,
              points: 0,
            },
            pull_requests_merged: {
              count: 1,
              points: 500,
            },
            social_media_contributions: {
              count: 0,
              points: 0,
            },
            node_uptime: {
              count: 0,
              points: 0,
            },
            send_transaction: {
              count: 0,
              points: 0,
            },
          },
        });
      });
    });

    describe('with a valid total request', () => {
      it('returns the total metrics for the user in the given range', async () => {
        const user = await usersService.create({
          email: faker.internet.email(),
          graffiti: uuid(),
          country_code: faker.address.countryCode('alpha-3'),
        });

        const start = new Date(Date.now() - 1).toISOString();
        const end = new Date().toISOString();
        const granularity = MetricsGranularity.TOTAL;

        const { body } = await request(app.getHttpServer())
          .get(`/users/${user.id}/metrics`)
          .query({
            granularity,
            start,
            end,
          });

        expect(body).toMatchObject({
          user_id: user.id,
          granularity,
          points: 0,
          metrics: {
            blocks_mined: {
              count: 0,
              points: 0,
            },
            bugs_caught: {
              count: 0,
              points: 0,
            },
            community_contributions: {
              count: 0,
              points: 0,
            },
            pull_requests_merged: {
              count: 0,
              points: 0,
            },
            social_media_contributions: {
              count: 0,
              points: 0,
            },
            node_uptime: {
              count: 0,
              points: 0,
            },
          },
        });
      });
    });
  });

  describe('GET /users', () => {
    const getLocation = async () => {
      const { body } = await request(app.getHttpServer())
        .get('/users')
        .expect(HttpStatus.OK);

      const { data } = body;
      const places = (data as Record<string, unknown>[]).map(
        ({ country_code: cc }) => cc,
      );
      return places[0] as string;
    };

    describe('with an invalid order by option', () => {
      it('returns a 422', async () => {
        const { body } = await request(app.getHttpServer())
          .get('/users')
          .query({
            order_by: 'foobar',
          })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with a valid request', () => {
      it('returns a list of users', async () => {
        const { body } = await request(app.getHttpServer())
          .get(`/users`)
          .expect(HttpStatus.OK);

        const { data } = body;
        expect((data as unknown[]).length).toBeGreaterThan(0);
        expect((data as unknown[])[0]).toMatchObject({
          id: expect.any(Number),
          graffiti: expect.any(String),
          created_at: expect.any(String),
        });
      });
    });

    describe('with `order_by` provided', () => {
      it('returns ranks with the users (and no country_code provided)', async () => {
        const { body } = await request(app.getHttpServer())
          .get(`/users`)
          .query({ order_by: 'rank' })
          .expect(HttpStatus.OK);

        const { data } = body;
        expect((data as unknown[]).length).toBeGreaterThan(0);
        expect((data as unknown[])[0]).toMatchObject({
          id: expect.any(Number),
          graffiti: expect.any(String),
          rank: expect.any(Number),
          created_at: expect.any(String),
        });
      });
    });

    describe('with `country_code` provided', () => {
      it('allows filtering by country_code', async () => {
        const someplace = await getLocation();
        const { body } = await request(app.getHttpServer())
          .get('/users')
          .query({ country_code: someplace })
          .expect(HttpStatus.OK);
        const { data } = body;
        (data as Record<string, unknown>[]).map(({ country_code }) =>
          expect(country_code).toEqual(someplace),
        );
      });
    });

    describe('when rank is provided', () => {
      it('filters by country code', async () => {
        const someplace = await getLocation();
        const { body } = await request(app.getHttpServer())
          .get('/users')
          .query({ country_code: someplace, order_by: 'rank' })
          .expect(HttpStatus.OK);
        const { data } = body;
        (data as Record<string, unknown>[]).map(({ country_code }) =>
          expect(country_code).toEqual(someplace),
        );
      });
    });

    describe('with an invalid cursor', () => {
      it('returns a 404', async () => {
        await request(app.getHttpServer())
          .get(`/users`)
          .query({ order_by: 'rank', before: -1 })
          .expect(HttpStatus.NOT_FOUND);
      });
    });
  });

  describe('POST /users', () => {
    describe('with missing arguments', () => {
      it('returns a 422', async () => {
        const { body } = await request(app.getHttpServer())
          .post(`/users`)
          .set('Authorization', `Bearer ${API_KEY}`)
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with empty arguments', () => {
      it('returns a 422', async () => {
        const { body } = await request(app.getHttpServer())
          .post(`/users`)
          .set('Authorization', `Bearer ${API_KEY}`)
          .send({ email: '', graffiti: '' })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with a duplicate email', () => {
      it('returns a 422', async () => {
        const user = await usersService.create({
          email: faker.internet.email(),
          graffiti: uuid(),
          country_code: faker.address.countryCode('alpha-3'),
        });
        await request(app.getHttpServer())
          .post(`/users`)
          .set('Authorization', `Bearer ${API_KEY}`)
          .send({ email: user.email, graffiti: user.graffiti })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);
      });
    });

    describe('with valid arguments', () => {
      it('creates a user', async () => {
        const email = faker.internet.email().toUpperCase();
        const graffiti = uuid();
        const discord = faker.internet.userName();
        const { body } = await request(app.getHttpServer())
          .post(`/users`)
          .set('Authorization', `Bearer ${API_KEY}`)
          .send({
            email,
            graffiti,
            discord,
            country_code: faker.address.countryCode('alpha-3'),
          })
          .expect(HttpStatus.CREATED);

        expect(body).toMatchObject({
          id: expect.any(Number),
          email: standardizeEmail(email),
          created_at: expect.any(String),
          graffiti,
          discord,
        });
      });
    });
  });

  describe('PUT /users/:id', () => {
    describe('with no logged in user', () => {
      it('returns a 401', async () => {
        const { body } = await request(app.getHttpServer())
          .put('/users/0')
          .expect(HttpStatus.UNAUTHORIZED);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with a mismatch in id', () => {
      it('returns a 403', async () => {
        const user = await usersService.create({
          email: faker.internet.email(),
          graffiti: uuid(),
          country_code: faker.address.countryCode('alpha-3'),
        });

        jest
          .spyOn(magicLinkService, 'getEmailFromHeader')
          .mockImplementationOnce(() => Promise.resolve(user.email));

        const { body } = await request(app.getHttpServer())
          .put('/users/0')
          .set('Authorization', 'token')
          .send({ discord: 'foo' })
          .expect(HttpStatus.FORBIDDEN);

        expect(body).toMatchSnapshot();
      });
    });

    describe('with an empty string graffiti', () => {
      it('returns a 422', async () => {
        const user = await usersService.create({
          email: faker.internet.email(),
          graffiti: uuid(),
          country_code: faker.address.countryCode('alpha-3'),
        });

        jest
          .spyOn(magicLinkService, 'getEmailFromHeader')
          .mockImplementationOnce(() => Promise.resolve(user.email));

        const { body } = await request(app.getHttpServer())
          .put(`/users/${user.id}`)
          .set('Authorization', 'token')
          .send({
            graffiti: '',
          })
          .expect(HttpStatus.UNPROCESSABLE_ENTITY);

        expect(body).toEqual({
          error: 'Unprocessable Entity',
          message: ['graffiti should not be empty'],
          statusCode: 422,
        });
      });
    });

    describe('with a valid payload', () => {
      it('updates the user', async () => {
        const user = await usersService.create({
          email: faker.internet.email(),
          graffiti: uuid(),
          country_code: faker.address.countryCode('alpha-3'),
        });

        jest
          .spyOn(magicLinkService, 'getEmailFromHeader')
          .mockImplementationOnce(() => Promise.resolve(user.email));

        const options = { discord: uuid() };
        const { body } = await request(app.getHttpServer())
          .put(`/users/${user.id}`)
          .set('Authorization', 'token')
          .send(options)
          .expect(HttpStatus.OK);

        expect(body).toMatchObject({
          id: user.id,
          discord: options.discord,
        });
      });
    });
  });
});