Skip to content

Commit

Permalink
updated service and added spec file
Browse files Browse the repository at this point in the history
  • Loading branch information
Jingo88 committed Nov 14, 2024
1 parent 535f7d9 commit b85e1e9
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { firstValueFrom, of } from "rxjs";

import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";

import {
FakeAccountService,
FakeSingleUserState,
FakeStateProvider,
mockAccountServiceWith,
} from "../../../common/spec";

import {
NewDeviceVerificationNoticeService,
NewDeviceVerificationNotice,
NEW_DEVICE_VERIFICATION_NOTICE_KEY,
} from "./new-device-verification-notice.service";

describe("New Device Verification Notice", () => {
const sut = NEW_DEVICE_VERIFICATION_NOTICE_KEY;
const userId = Utils.newGuid() as UserId;
const mockUserId$ = of(userId);
let newDeviceVerificationService: NewDeviceVerificationNoticeService;
let mockNoticeState: FakeSingleUserState<NewDeviceVerificationNotice>;
let stateProvider: FakeStateProvider;
let accountService: FakeAccountService;

beforeEach(() => {
accountService = mockAccountServiceWith(userId);
stateProvider = new FakeStateProvider(accountService);
mockNoticeState = stateProvider.singleUser.getFake(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY);
newDeviceVerificationService = new NewDeviceVerificationNoticeService(stateProvider);
});

it("should deserialize newDeviceVerificationNotice values", async () => {
const currentDate = new Date();
const inputObj = {
last_dismissal: currentDate,
permanent_dismissal: false,
};

const expectedFolderData = {
last_dismissal: currentDate.toJSON(),
permanent_dismissal: false,
};

const result = sut.deserializer(JSON.parse(JSON.stringify(inputObj)));

expect(result).toEqual(expectedFolderData);
});

describe("notice$", () => {
it("emits new device verification notice state", async () => {
const currentDate = new Date();
const data = {
last_dismissal: currentDate,
permanent_dismissal: false,
};
await stateProvider.setUserState(NEW_DEVICE_VERIFICATION_NOTICE_KEY, data, userId);

const result = await firstValueFrom(newDeviceVerificationService.noticeState$(mockUserId$));

expect(result).toBe(data);
});
});

describe("update notice state", () => {
it("should update the date with a new value", async () => {
const currentDate = new Date();
const oldDate = new Date("11-11-2011");
const oldState = {
last_dismissal: oldDate,
permanent_dismissal: false,
};
const newState = {
last_dismissal: currentDate,
permanent_dismissal: true,
};
mockNoticeState.nextState(oldState);
await newDeviceVerificationService.updateNewDeviceVerificationNoticeState(userId, newState);

const result = await firstValueFrom(newDeviceVerificationService.noticeState$(mockUserId$));
expect(result).toEqual(newState);
});
});
});
64 changes: 45 additions & 19 deletions libs/vault/src/services/new-device-verification-notice.service.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,67 @@
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { Observable, switchMap, takeWhile } from "rxjs";
import { Jsonify } from "type-fest";

import {
ActiveUserState,
StateProvider,
UserKeyDefinition,
NEW_DEVICE_VERIFICATION_NOTICE,
SingleUserState,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";

export type NewDeviceVerificationNotice = {
last_dismissal: string;
// This service checks when to show New Device Verification Notice to Users
// It will be a two phase approach and the values below will work with two different feature flags
// If a user dismisses the notice, use "last_dismissal" to wait 7 days before re-prompting
// permanent_dismissal will be checked if the user should never see the notice again
export class NewDeviceVerificationNotice {
last_dismissal: Date;
permanent_dismissal: boolean;
};

const NEW_DEVICE_VERIFICATION_NOTICE_KEY = new UserKeyDefinition<NewDeviceVerificationNotice>(
NEW_DEVICE_VERIFICATION_NOTICE,
"noticeState",
{
deserializer: (jsonData) => jsonData,
clearOn: [],
},
);
constructor(obj: Partial<NewDeviceVerificationNotice>) {
if (obj == null) {
return;

Check warning on line 23 in libs/vault/src/services/new-device-verification-notice.service.ts

View check run for this annotation

Codecov / codecov/patch

libs/vault/src/services/new-device-verification-notice.service.ts#L23

Added line #L23 was not covered by tests
}
this.last_dismissal = obj.last_dismissal || null;
this.permanent_dismissal = obj.permanent_dismissal || null;
}

static fromJSON(obj: Jsonify<NewDeviceVerificationNotice>) {
return Object.assign(new NewDeviceVerificationNotice({}), obj);
}
}

export const NEW_DEVICE_VERIFICATION_NOTICE_KEY =
new UserKeyDefinition<NewDeviceVerificationNotice>(
NEW_DEVICE_VERIFICATION_NOTICE,
"noticeState",
{
deserializer: (obj: Jsonify<NewDeviceVerificationNotice>) =>
NewDeviceVerificationNotice.fromJSON(obj),
clearOn: [],
},
);

@Injectable()
export class NewDeviceVerificationNoticeService {
private noticeState: ActiveUserState<NewDeviceVerificationNotice>;
noticeState$: Observable<NewDeviceVerificationNotice>;
constructor(private stateProvider: StateProvider) {}

private noticeState(userId: UserId): SingleUserState<NewDeviceVerificationNotice> {
return this.stateProvider.getUser(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY);
}

constructor(private stateProvider: StateProvider) {
this.noticeState = this.stateProvider.getActive(NEW_DEVICE_VERIFICATION_NOTICE_KEY);
this.noticeState$ = this.noticeState.state$;
noticeState$(userId$: Observable<UserId>): Observable<NewDeviceVerificationNotice> {
return userId$.pipe(
takeWhile((userId) => userId != null),
switchMap((userId) => this.noticeState(userId).state$),
);
}

async updateNewDeviceVerificationNoticeState(
userId: UserId,
newState: NewDeviceVerificationNotice,
): Promise<void> {
await this.noticeState.update(() => {
await this.noticeState(userId).update(() => {
return { ...newState };
});
}
Expand Down

0 comments on commit b85e1e9

Please sign in to comment.