diff --git a/packages/backend/src/controllers/api/v1/users/get-user-trial.ee.js b/packages/backend/src/controllers/api/v1/users/get-user-trial.ee.js new file mode 100644 index 00000000..1c4575bb --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/get-user-trial.ee.js @@ -0,0 +1,12 @@ +import { renderObject } from '../../../../helpers/renderer.js'; + +export default async (request, response) => { + const inTrial = await request.currentUser.inTrial(); + + const trialInfo = { + inTrial, + expireAt: request.currentUser.trialExpiryDate, + }; + + renderObject(response, trialInfo); +}; diff --git a/packages/backend/src/controllers/api/v1/users/get-user-trial.ee.test.js b/packages/backend/src/controllers/api/v1/users/get-user-trial.ee.test.js new file mode 100644 index 00000000..52e300b6 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/get-user-trial.ee.test.js @@ -0,0 +1,38 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; +import app from '../../../../app.js'; +import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js'; +import { createUser } from '../../../../../test/factories/user.js'; +import getUserTrialMock from '../../../../../test/mocks/rest/api/v1/users/get-user-trial.js'; +import appConfig from '../../../../config/app.js'; +import { DateTime } from 'luxon'; +import User from '../../../../models/user.js'; + +describe('GET /api/v1/users/:userId/trial', () => { + let user, token; + + beforeEach(async () => { + const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate(); + user = await createUser({ trialExpiryDate }); + token = createAuthTokenByUserId(user.id); + + vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); + }); + + describe('should return in trial, active subscription and expire at info', () => { + beforeEach(async () => { + vi.spyOn(User.prototype, 'inTrial').mockResolvedValue(false); + vi.spyOn(User.prototype, 'hasActiveSubscription').mockResolvedValue(true); + }); + + it('should return null', async () => { + const response = await request(app) + .get(`/api/v1/users/${user.id}/trial`) + .set('Authorization', token) + .expect(200); + + const expectedResponsePayload = await getUserTrialMock(user); + expect(response.body).toEqual(expectedResponsePayload); + }); + }); +}); diff --git a/packages/backend/src/helpers/authentication.js b/packages/backend/src/helpers/authentication.js index 010856ee..42708a0b 100644 --- a/packages/backend/src/helpers/authentication.js +++ b/packages/backend/src/helpers/authentication.js @@ -20,7 +20,8 @@ export const isAuthenticated = async (_parent, _args, req) => { .withGraphFetched({ role: true, permissions: true, - }); + }) + .throwIfNotFound(); return true; } catch (error) { diff --git a/packages/backend/src/helpers/authentication.test.js b/packages/backend/src/helpers/authentication.test.js index 80ce8c18..691a7628 100644 --- a/packages/backend/src/helpers/authentication.test.js +++ b/packages/backend/src/helpers/authentication.test.js @@ -1,11 +1,8 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { allow } from 'graphql-shield'; -import jwt from 'jsonwebtoken'; -import User from '../models/user.js'; import { isAuthenticated, authenticationRules } from './authentication.js'; - -vi.mock('jsonwebtoken'); -vi.mock('../models/user.js'); +import { createUser } from '../../test/factories/user.js'; +import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id.js'; describe('isAuthenticated', () => { it('should return false if no token is provided', async () => { @@ -14,29 +11,26 @@ describe('isAuthenticated', () => { }); it('should return false if token is invalid', async () => { - jwt.verify.mockImplementation(() => { - throw new Error('invalid token'); - }); - const req = { headers: { authorization: 'invalidToken' } }; expect(await isAuthenticated(null, null, req)).toBe(false); }); - it('should return true if token is valid', async () => { - jwt.verify.mockReturnValue({ userId: '123' }); + it('should return true if token is valid and there is a user', async () => { + const user = await createUser(); + const token = createAuthTokenByUserId(user.id); - User.query.mockReturnValue({ - findById: vi.fn().mockReturnValue({ - leftJoinRelated: vi.fn().mockReturnThis(), - withGraphFetched: vi - .fn() - .mockResolvedValue({ id: '123', role: {}, permissions: {} }), - }), - }); - - const req = { headers: { authorization: 'validToken' } }; + const req = { headers: { authorization: token } }; expect(await isAuthenticated(null, null, req)).toBe(true); }); + + it('should return false if token is valid and but there is no user', async () => { + const user = await createUser(); + const token = createAuthTokenByUserId(user.id); + await user.$query().delete(); + + const req = { headers: { authorization: token } }; + expect(await isAuthenticated(null, null, req)).toBe(false); + }); }); describe('authentication rules', () => { diff --git a/packages/backend/src/helpers/check-is-cloud.js b/packages/backend/src/helpers/check-is-cloud.js new file mode 100644 index 00000000..f0b93b56 --- /dev/null +++ b/packages/backend/src/helpers/check-is-cloud.js @@ -0,0 +1,11 @@ +import appConfig from '../config/app.js'; + +export const checkIsCloud = async (request, response, next) => { + if (appConfig.isCloud) { + next(); + } else { + return response.status(404).end(); + } +}; + +export default checkIsCloud; diff --git a/packages/backend/src/routes/api/v1/users.js b/packages/backend/src/routes/api/v1/users.js index fb1ece18..97eb8895 100644 --- a/packages/backend/src/routes/api/v1/users.js +++ b/packages/backend/src/routes/api/v1/users.js @@ -1,14 +1,22 @@ import { Router } from 'express'; import { authenticateUser } from '../../../helpers/authentication.js'; import { authorizeUser } from '../../../helpers/authorization.js'; +import checkIsCloud from '../../../helpers/check-is-cloud.js'; import getCurrentUserAction from '../../../controllers/api/v1/users/get-current-user.js'; import getUserAction from '../../../controllers/api/v1/users/get-user.js'; import getUsersAction from '../../../controllers/api/v1/users/get-users.js'; +import getUserTrialAction from '../../../controllers/api/v1/users/get-user-trial.ee.js'; const router = Router(); router.get('/', authenticateUser, authorizeUser, getUsersAction); router.get('/me', authenticateUser, getCurrentUserAction); router.get('/:userId', authenticateUser, authorizeUser, getUserAction); +router.get( + '/:userId/trial', + authenticateUser, + checkIsCloud, + getUserTrialAction +); export default router; diff --git a/packages/backend/test/mocks/rest/api/v1/users/get-user-trial.js b/packages/backend/test/mocks/rest/api/v1/users/get-user-trial.js new file mode 100644 index 00000000..7721aaf7 --- /dev/null +++ b/packages/backend/test/mocks/rest/api/v1/users/get-user-trial.js @@ -0,0 +1,17 @@ +const getUserTrialMock = async (currentUser) => { + return { + data: { + inTrial: await currentUser.inTrial(), + expireAt: currentUser.trialExpiryDate.toISOString(), + }, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'Object', + }, + }; +}; + +export default getUserTrialMock;