Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/logs/exceptions/log-requires-premium.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Exception } from '../../utils/exception';

export class LogRequiresPremiumException extends Exception {
constructor() {
super('This log requires a premium account to access because it is either still running or has been archived.');
}
}
101 changes: 93 additions & 8 deletions src/logs/logs.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { STATUS } from './entities/status.entity';
import { LastSyncDto } from './dto/last-sync.dto';
import { TYPE } from './entities/type.entity';
import { HISTORY_TYPE } from './entities/history-type.entity';
import { PremiumService } from '../premium/premium.service';
import { PremiumRequiredException } from '../premium/exceptions/premium-required.exception';

describe('LogsController', () => {
let accountService: AccountService;
Expand All @@ -25,6 +27,10 @@ describe('LogsController', () => {
let chargeLogId: string;
let syncTimestamp: Date;

const mockPremiumService = {
getExpiryDate: jest.fn(),
};

async function createAccount() {
const dto = new CreateAccountDto();

Expand All @@ -35,14 +41,19 @@ describe('LogsController', () => {
}

beforeEach(async () => {
mockPremiumService.getExpiryDate.mockReset();

const module: TestingModule = await Test.createTestingModule({
imports: [
LogsModule,
ConfigModule.forRoot(),
MongooseModule.forRoot(process.env.DATABASE_URI),
EventEmitterModule.forRoot(),
],
}).compile();
})
.overrideProvider(PremiumService)
.useValue(mockPremiumService)
.compile();

accountService = module.get<AccountService>(AccountService);
controller = module.get<LogsController>(LogsController);
Expand Down Expand Up @@ -113,7 +124,15 @@ describe('LogsController', () => {
expect(response).toBeUndefined();
});

it('should be able to retrieve log in list', async () => {
it('should not be able to retrieve running log in list when not premium', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(null);
const response = await controller.findAll(testAccount.akey);

expect(response).toHaveLength(0);
});

it('should be able to retrieve running log in list when premium', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findAll(testAccount.akey);

expect(response).toHaveLength(1);
Expand All @@ -126,19 +145,36 @@ describe('LogsController', () => {
});

it('should be able to filter out log in list', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findAll(testAccount.akey, TYPE.DRIVE);

expect(response).toHaveLength(0);
});

it('should be able to retrieve it', async () => {
it('should not be able to retrieve running log when not premium', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(null);
await expect(async () => {
await controller.findOne(testAccount.akey, logId);
}).rejects.toThrow(PremiumRequiredException);
});

it('should be able to retrieve log explicitly', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOne(testAccount.akey, logId);

expect(response).toBeInstanceOf(LogDto);
expect(response).not.toHaveProperty('history');
});

it('should be able to retrieve log history', async () => {
it('should not be able to retrieve log history when not premium', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(null);
await expect(async () => {
await controller.findOneWithHistory(testAccount.akey, logId);
}).rejects.toThrow(PremiumRequiredException);
});

it('should be able to retrieve log history when premium', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOneWithHistory(
testAccount.akey,
logId,
Expand All @@ -149,7 +185,20 @@ describe('LogsController', () => {
expect(response[0]).toHaveProperty('timestamp');
});

it('should be able to retrieve last sync data', async () => {
it('should be able to retrieve last sync data when not premium', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(null);
const response = await controller.lastSync(testAccount.akey);

expect(response).toBeInstanceOf(LastSyncDto);
expect(response).toHaveProperty('updatedAt');
expect(response).toHaveProperty('socDisplay', 80);
expect(new Date(response.updatedAt).getTime()).toBeGreaterThan(
syncTimestamp.getTime(),
);
});

it('should be able to retrieve last sync data when premium', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.lastSync(testAccount.akey);

expect(response).toBeInstanceOf(LastSyncDto);
Expand All @@ -173,6 +222,7 @@ describe('LogsController', () => {
});

it('should be able to retrieve log history with timestamp', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOneWithHistory(
testAccount.akey,
logId,
Expand All @@ -188,6 +238,7 @@ describe('LogsController', () => {
});

it('should be able to retrieve location log history', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOneWithHistory(
testAccount.akey,
logId,
Expand All @@ -204,6 +255,7 @@ describe('LogsController', () => {
});

it('should be able to retrieve battery log history', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOneWithHistory(
testAccount.akey,
logId,
Expand All @@ -215,6 +267,7 @@ describe('LogsController', () => {
});

it('should be able to retrieve all log history', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOneWithHistory(
testAccount.akey,
logId,
Expand All @@ -236,6 +289,7 @@ describe('LogsController', () => {
});

it('should contain new title', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOne(testAccount.akey, logId);

expect(response).toHaveProperty('title', 'My title');
Expand All @@ -260,8 +314,13 @@ describe('LogsController', () => {

await controller.syncData(testAccount.akey, dto);

const response = await controller.findAll(testAccount.akey);
let response = await controller.findAll(testAccount.akey);

expect(response).toHaveLength(0);

mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));

response = await controller.findAll(testAccount.akey);
expect(response).toHaveLength(1);
expect(response.at(0)).toHaveProperty('type', TYPE.UNKNOWN);

Expand All @@ -278,6 +337,7 @@ describe('LogsController', () => {
});

it('should contain two history records', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOneWithHistory(
testAccount.akey,
logId,
Expand All @@ -288,7 +348,20 @@ describe('LogsController', () => {
expect(response.at(1)).toHaveProperty('socDisplay', 81);
});

it('should be able to retrieve updated last sync data', async () => {
it('should be able to retrieve updated last sync data when not premium', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(null);
const response = await controller.lastSync(testAccount.akey);

expect(response).toBeInstanceOf(LastSyncDto);
expect(response).toHaveProperty('updatedAt');
expect(response).toHaveProperty('socDisplay', 81);
expect(new Date(response.updatedAt).getTime()).toBeGreaterThan(
syncTimestamp.getTime(),
);
});

it('should be able to retrieve updated last sync data when premium', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.lastSync(testAccount.akey);

expect(response).toBeInstanceOf(LastSyncDto);
Expand All @@ -306,6 +379,7 @@ describe('LogsController', () => {

await controller.syncData(testAccount.akey, dto);

mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findAll(testAccount.akey);

expect(response).toHaveLength(1);
Expand All @@ -324,8 +398,13 @@ describe('LogsController', () => {

await new Promise((resolve) => setTimeout(resolve, 1000));

const response = await controller.findAll(testAccount.akey);
mockPremiumService.getExpiryDate.mockResolvedValue(null);
let response = await controller.findAll(testAccount.akey);

expect(response).toHaveLength(1);

mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
response = await controller.findAll(testAccount.akey);
expect(response).toHaveLength(2);
expect(response.at(0)).toHaveProperty('type', TYPE.CHARGE);
chargeLogId = response[0].id;
Expand All @@ -340,6 +419,7 @@ describe('LogsController', () => {
});

it('should contain metadata', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOne(testAccount.akey, chargeLogId);

expect(response).toBeInstanceOf(LogDto);
Expand All @@ -366,6 +446,7 @@ describe('LogsController', () => {

await new Promise((resolve) => setTimeout(resolve, 1000));

mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOne(testAccount.akey, chargeLogId);

expect(response).toBeInstanceOf(LogDto);
Expand All @@ -386,6 +467,7 @@ describe('LogsController', () => {

await new Promise((resolve) => setTimeout(resolve, 1000));

mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOne(testAccount.akey, chargeLogId);

expect(response).toBeInstanceOf(LogDto);
Expand All @@ -401,6 +483,7 @@ describe('LogsController', () => {

await new Promise((resolve) => setTimeout(resolve, 1000));

mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOne(testAccount.akey, chargeLogId);

expect(response).toBeInstanceOf(LogDto);
Expand All @@ -417,13 +500,15 @@ describe('LogsController', () => {

await new Promise((resolve) => setTimeout(resolve, 1000));

mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findOne(testAccount.akey, chargeLogId);

expect(response).toBeInstanceOf(LogDto);
expect(response).toHaveProperty('distance', 1.65);
});

it('should find current running log', async () => {
mockPremiumService.getExpiryDate.mockResolvedValue(new Date('2099-12-31'));
const response = await controller.findRunning(testAccount.akey);

expect(response).toBeInstanceOf(LogDto);
Expand Down
6 changes: 6 additions & 0 deletions src/logs/logs.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { LogNotRunningException } from './exceptions/log-not-running.exception';
import { HISTORY_TYPE } from './entities/history-type.entity';
import { PremiumGuard } from '../premium/premium.guard';
import { Premium } from '../premium/decorators/premium.decorator';
import { LogRequiresPremiumException } from './exceptions/log-requires-premium.exception';
import { PremiumRequiredException } from '../premium/exceptions/premium-required.exception';

@Controller('logs')
@UseGuards(AuthGuard)
Expand Down Expand Up @@ -69,6 +71,8 @@ export class LogsController {
} catch (error) {
if (error instanceof LogNotExistsException) {
throw new NotFoundException(error.message);
} else if (error instanceof LogRequiresPremiumException) {
throw new PremiumRequiredException(error.message);
}

throw new InternalServerErrorException();
Expand All @@ -87,6 +91,8 @@ export class LogsController {
} catch (error) {
if (error instanceof LogNotExistsException) {
throw new NotFoundException(error.message);
} else if (error instanceof LogRequiresPremiumException) {
throw new PremiumRequiredException(error.message);
}

throw new InternalServerErrorException();
Expand Down
41 changes: 40 additions & 1 deletion src/logs/logs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ import { Sync } from './schemas/sync.schema';
import { TYPE } from './entities/type.entity';
import { LogNotRunningException } from './exceptions/log-not-running.exception';
import { HISTORY_TYPE } from './entities/history-type.entity';
import { PremiumService } from '../premium/premium.service';
import { LogRequiresPremiumException } from './exceptions/log-requires-premium.exception';

@Injectable()
export class LogsService {
constructor(
@InjectModel(Log.name) private logModel: Model<Log>,
@InjectModel(LastSync.name) private lastSyncModel: Model<LastSync>,
private readonly premiumService: PremiumService,
private readonly eventEmitter: EventEmitter2,
) {}

Expand Down Expand Up @@ -117,7 +120,19 @@ export class LogsService {
logs.where({ type });
}

return Promise.resolve((await logs).map((log) => new LogDto(log)));
const isPremium = await this.premiumService.getExpiryDate(akey);

const logsToReturn = [];

(await logs).forEach((log) => {
if (log.status === STATUS.RUNNING && !isPremium) {
// Skip running logs for non-premium users
} else {
logsToReturn.push(log);
}
});

return Promise.resolve(logsToReturn.map((log) => new LogDto(log)));
}

async findOne(akey: string, id: string): Promise<LogDto> {
Expand All @@ -129,12 +144,36 @@ export class LogsService {
throw new LogNotExistsException();
}

if (log.status === STATUS.RUNNING) {
const isPremium = await this.premiumService.getExpiryDate(akey);

if (!isPremium) {
throw new LogRequiresPremiumException();
}
}

return Promise.resolve(new LogDto(log));
}

async findOneWithHistory(akey: string, id: string, type: HISTORY_TYPE = HISTORY_TYPE.ALL): Promise<Sync[]> {
let history = [];

const logStatus = await this.logModel
.findOne({ akey, _id: id })
.select('status');

if (!logStatus) {
throw new LogNotExistsException();
}

if (logStatus.status === STATUS.RUNNING) {
const isPremium = await this.premiumService.getExpiryDate(akey);

if (!isPremium) {
throw new LogRequiresPremiumException();
}
}

switch (type) {
case HISTORY_TYPE.ALL:
const logWithAllHistory = await this.logModel
Expand Down
4 changes: 2 additions & 2 deletions src/premium/exceptions/premium-required.exception.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HttpException, HttpStatus } from '@nestjs/common';

export class PremiumRequiredException extends HttpException {
constructor() {
super('Active premium status required.', HttpStatus.PAYMENT_REQUIRED);
constructor( message?: string) {
super(message || 'Active premium status required.', HttpStatus.PAYMENT_REQUIRED);
}
}