mirror of
https://github.com/bitwarden/browser
synced 2026-01-29 15:53:45 +00:00
WIP respond to Claude & add tests
This commit is contained in:
365
apps/cli/src/tools/send/commands/create.command.spec.ts
Normal file
365
apps/cli/src/tools/send/commands/create.command.spec.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { mockAccountInfoWith } from "@bitwarden/common/spec";
|
||||
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||
import { AuthType } from "@bitwarden/common/tools/send/models/domain/send";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { SendCreateCommand } from "./create.command";
|
||||
|
||||
describe("SendCreateCommand", () => {
|
||||
let command: SendCreateCommand;
|
||||
|
||||
const sendService = mock<SendService>();
|
||||
const environmentService = mock<EnvironmentService>();
|
||||
const sendApiService = mock<SendApiService>();
|
||||
const accountProfileService = mock<BillingAccountProfileStateService>();
|
||||
const accountService = mock<AccountService>();
|
||||
|
||||
const activeAccount = {
|
||||
id: "user-id" as UserId,
|
||||
...mockAccountInfoWith({
|
||||
email: "user@example.com",
|
||||
name: "User",
|
||||
}),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
accountService.activeAccount$ = of(activeAccount);
|
||||
accountProfileService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||
environmentService.environment$ = of({
|
||||
getWebVaultUrl: () => "https://vault.bitwarden.com",
|
||||
} as any);
|
||||
|
||||
command = new SendCreateCommand(
|
||||
sendService,
|
||||
environmentService,
|
||||
sendApiService,
|
||||
accountProfileService,
|
||||
accountService,
|
||||
);
|
||||
});
|
||||
|
||||
describe("authType inference", () => {
|
||||
const futureDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
describe("with CLI flags", () => {
|
||||
it("should set authType to Email when emails are provided via CLI", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
};
|
||||
|
||||
const cmdOptions = {
|
||||
email: ["test@example.com"],
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: "send-id", emails: "test@example.com", authType: AuthType.Email } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
sendService.getFromState.mockResolvedValue({
|
||||
decrypt: jest.fn().mockResolvedValue({}),
|
||||
} as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
expect(sendService.encrypt).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: SendType.Text,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it("should set authType to Password when password is provided via CLI", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
};
|
||||
|
||||
const cmdOptions = {
|
||||
password: "testPassword123",
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: "send-id", authType: AuthType.Password } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
sendService.getFromState.mockResolvedValue({
|
||||
decrypt: jest.fn().mockResolvedValue({}),
|
||||
} as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
expect(sendService.encrypt).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
null as any,
|
||||
"testPassword123",
|
||||
);
|
||||
});
|
||||
|
||||
it("should set authType to None when neither emails nor password provided", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
};
|
||||
|
||||
const cmdOptions = {};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: "send-id", authType: AuthType.None } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
sendService.getFromState.mockResolvedValue({
|
||||
decrypt: jest.fn().mockResolvedValue({}),
|
||||
} as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
expect(sendService.encrypt).toHaveBeenCalledWith(expect.any(Object), null, undefined);
|
||||
});
|
||||
|
||||
it("should return error when both emails and password provided via CLI", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
};
|
||||
|
||||
const cmdOptions = {
|
||||
email: ["test@example.com"],
|
||||
password: "testPassword123",
|
||||
};
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("--password and --emails are mutually exclusive.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with JSON input", () => {
|
||||
it("should set authType to Email when emails provided in JSON", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
emails: ["test@example.com", "another@example.com"],
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{
|
||||
id: "send-id",
|
||||
emails: "test@example.com,another@example.com",
|
||||
authType: AuthType.Email,
|
||||
} as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
sendService.getFromState.mockResolvedValue({
|
||||
decrypt: jest.fn().mockResolvedValue({}),
|
||||
} as any);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
});
|
||||
|
||||
it("should set authType to Password when password provided in JSON", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
password: "jsonPassword123",
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: "send-id", authType: AuthType.Password } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
sendService.getFromState.mockResolvedValue({
|
||||
decrypt: jest.fn().mockResolvedValue({}),
|
||||
} as any);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
});
|
||||
|
||||
it("should return error when both emails and password provided in JSON", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
emails: ["test@example.com"],
|
||||
password: "jsonPassword123",
|
||||
};
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("--password and --emails are mutually exclusive.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with mixed CLI and JSON input", () => {
|
||||
it("should return error when CLI emails combined with JSON password", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
password: "jsonPassword123",
|
||||
};
|
||||
|
||||
const cmdOptions = {
|
||||
email: ["cli@example.com"],
|
||||
};
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("--password and --emails are mutually exclusive.");
|
||||
});
|
||||
|
||||
it("should return error when CLI password combined with JSON emails", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
emails: ["json@example.com"],
|
||||
};
|
||||
|
||||
const cmdOptions = {
|
||||
password: "cliPassword123",
|
||||
};
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("--password and --emails are mutually exclusive.");
|
||||
});
|
||||
|
||||
it("should use CLI value when JSON has different value of same type", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
emails: ["json@example.com"],
|
||||
};
|
||||
|
||||
const cmdOptions = {
|
||||
email: ["cli@example.com"],
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: "send-id", emails: "cli@example.com", authType: AuthType.Email } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
sendService.getFromState.mockResolvedValue({
|
||||
decrypt: jest.fn().mockResolvedValue({}),
|
||||
} as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases", () => {
|
||||
it("should set authType to None when emails array is empty", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
emails: [] as string[],
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: "send-id", authType: AuthType.None } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
sendService.getFromState.mockResolvedValue({
|
||||
decrypt: jest.fn().mockResolvedValue({}),
|
||||
} as any);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
});
|
||||
|
||||
it("should set authType to None when password is empty string", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
};
|
||||
|
||||
const cmdOptions = {
|
||||
password: "",
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: "send-id", authType: AuthType.None } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
sendService.getFromState.mockResolvedValue({
|
||||
decrypt: jest.fn().mockResolvedValue({}),
|
||||
} as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
});
|
||||
|
||||
it("should set authType to None when password is whitespace only", async () => {
|
||||
const requestJson = {
|
||||
type: SendType.Text,
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: futureDate,
|
||||
};
|
||||
|
||||
const cmdOptions = {
|
||||
password: " ",
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: "send-id", authType: AuthType.None } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
sendService.getFromState.mockResolvedValue({
|
||||
decrypt: jest.fn().mockResolvedValue({}),
|
||||
} as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -150,6 +150,7 @@ export class SendCreateCommand {
|
||||
encSend.deletionDate = sendView.deletionDate;
|
||||
encSend.expirationDate = sendView.expirationDate;
|
||||
encSend.emails = emails && emails.join(",");
|
||||
encSend.authType = req.authType;
|
||||
|
||||
await this.sendApiService.save([encSend, fileData]);
|
||||
const newSend = await this.sendService.getFromState(encSend.id);
|
||||
|
||||
400
apps/cli/src/tools/send/commands/edit.command.spec.ts
Normal file
400
apps/cli/src/tools/send/commands/edit.command.spec.ts
Normal file
@@ -0,0 +1,400 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { mockAccountInfoWith } from "@bitwarden/common/spec";
|
||||
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||
import { AuthType } from "@bitwarden/common/tools/send/models/domain/send";
|
||||
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { Response } from "../../../models/response";
|
||||
import { SendResponse } from "../models/send.response";
|
||||
|
||||
import { SendEditCommand } from "./edit.command";
|
||||
import { SendGetCommand } from "./get.command";
|
||||
|
||||
describe("SendEditCommand", () => {
|
||||
let command: SendEditCommand;
|
||||
|
||||
const sendService = mock<SendService>();
|
||||
const getCommand = mock<SendGetCommand>();
|
||||
const sendApiService = mock<SendApiService>();
|
||||
const accountProfileService = mock<BillingAccountProfileStateService>();
|
||||
const accountService = mock<AccountService>();
|
||||
|
||||
const activeAccount = {
|
||||
id: "user-id" as UserId,
|
||||
...mockAccountInfoWith({
|
||||
email: "user@example.com",
|
||||
name: "User",
|
||||
}),
|
||||
};
|
||||
|
||||
const mockSendId = "send-123";
|
||||
const mockSendView = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
text: { text: "test content", hidden: false },
|
||||
deletionDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
||||
} as SendView;
|
||||
|
||||
const mockSend = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
decrypt: jest.fn().mockResolvedValue(mockSendView),
|
||||
};
|
||||
|
||||
const encodeRequest = (data: any) => Buffer.from(JSON.stringify(data)).toString("base64");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
accountService.activeAccount$ = of(activeAccount);
|
||||
accountProfileService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||
sendService.getFromState.mockResolvedValue(mockSend as any);
|
||||
getCommand.run.mockResolvedValue(Response.success(new SendResponse(mockSendView)) as any);
|
||||
|
||||
command = new SendEditCommand(
|
||||
sendService,
|
||||
getCommand,
|
||||
sendApiService,
|
||||
accountProfileService,
|
||||
accountService,
|
||||
);
|
||||
});
|
||||
|
||||
describe("authType inference", () => {
|
||||
describe("with CLI flags", () => {
|
||||
it("should set authType to Email when emails are provided via CLI", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const cmdOptions = {
|
||||
email: ["test@example.com"],
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: mockSendId, emails: "test@example.com", authType: AuthType.Email } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const savedCall = sendApiService.save.mock.calls[0][0];
|
||||
expect(savedCall[0].authType).toBe(AuthType.Email);
|
||||
expect(savedCall[0].emails).toBe("test@example.com");
|
||||
});
|
||||
|
||||
it("should set authType to Password when password is provided via CLI", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const cmdOptions = {
|
||||
password: "testPassword123",
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: mockSendId, authType: AuthType.Password } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const savedCall = sendApiService.save.mock.calls[0][0];
|
||||
expect(savedCall[0].authType).toBe(AuthType.Password);
|
||||
});
|
||||
|
||||
it("should set authType to None when neither emails nor password provided", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const cmdOptions = {};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: mockSendId, authType: AuthType.None } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const savedCall = sendApiService.save.mock.calls[0][0];
|
||||
expect(savedCall[0].authType).toBe(AuthType.None);
|
||||
});
|
||||
|
||||
it("should return error when both emails and password provided via CLI", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const cmdOptions = {
|
||||
email: ["test@example.com"],
|
||||
password: "testPassword123",
|
||||
};
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("--password and --emails are mutually exclusive.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with JSON input", () => {
|
||||
it("should set authType to Email when emails provided in JSON", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
emails: ["test@example.com", "another@example.com"],
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: mockSendId, authType: AuthType.Password } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const savedCall = sendApiService.save.mock.calls[0][0];
|
||||
expect(savedCall[0].authType).toBe(AuthType.Email);
|
||||
});
|
||||
|
||||
it("should set authType to Password when password provided in JSON", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
password: "jsonPassword123",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: mockSendId, authType: AuthType.Password } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const savedCall = sendApiService.save.mock.calls[0][0];
|
||||
expect(savedCall[0].authType).toBe(AuthType.Password);
|
||||
});
|
||||
|
||||
it("should return error when both emails and password provided in JSON", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
emails: ["test@example.com"],
|
||||
password: "jsonPassword123",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("--password and --emails are mutually exclusive.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with mixed CLI and JSON input", () => {
|
||||
it("should return error when CLI emails combined with JSON password", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
password: "jsonPassword123",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const cmdOptions = {
|
||||
email: ["cli@example.com"],
|
||||
};
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("--password and --emails are mutually exclusive.");
|
||||
});
|
||||
|
||||
it("should return error when CLI password combined with JSON emails", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
emails: ["json@example.com"],
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const cmdOptions = {
|
||||
password: "cliPassword123",
|
||||
};
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("--password and --emails are mutually exclusive.");
|
||||
});
|
||||
|
||||
it("should prioritize CLI value when JSON has different value of same type", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
emails: ["json@example.com"],
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const cmdOptions = {
|
||||
email: ["cli@example.com"],
|
||||
};
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: mockSendId, emails: "cli@example.com", authType: AuthType.Email } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
|
||||
const response = await command.run(requestJson, cmdOptions);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const savedCall = sendApiService.save.mock.calls[0][0];
|
||||
expect(savedCall[0].authType).toBe(AuthType.Email);
|
||||
expect(savedCall[0].emails).toBe("cli@example.com");
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases", () => {
|
||||
it("should set authType to None when emails array is empty", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
emails: [] as string[],
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: mockSendId, authType: AuthType.None } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const savedCall = sendApiService.save.mock.calls[0][0];
|
||||
expect(savedCall[0].authType).toBe(AuthType.None);
|
||||
});
|
||||
|
||||
it("should set authType to None when password is empty string", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
password: "",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
sendService.encrypt.mockResolvedValue([
|
||||
{ id: mockSendId, authType: AuthType.None } as any,
|
||||
null as any,
|
||||
]);
|
||||
sendApiService.save.mockResolvedValue(undefined as any);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const savedCall = sendApiService.save.mock.calls[0][0];
|
||||
expect(savedCall[0].authType).toBe(AuthType.None);
|
||||
});
|
||||
|
||||
it("should handle send not found", async () => {
|
||||
sendService.getFromState.mockResolvedValue(null);
|
||||
|
||||
const requestData = {
|
||||
id: "nonexistent-id",
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
});
|
||||
|
||||
it("should handle type mismatch", async () => {
|
||||
const requestData = {
|
||||
id: mockSendId,
|
||||
type: SendType.File,
|
||||
name: "Test Send",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("Cannot change a Send's type");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("validation", () => {
|
||||
it("should return error when requestJson is empty", async () => {
|
||||
// Set BW_SERVE to prevent readStdin call
|
||||
process.env.BW_SERVE = "true";
|
||||
|
||||
const response = await command.run("", {});
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("`requestJson` was not provided.");
|
||||
|
||||
delete process.env.BW_SERVE;
|
||||
});
|
||||
|
||||
it("should return error when id is not provided", async () => {
|
||||
const requestData = {
|
||||
type: SendType.Text,
|
||||
name: "Test Send",
|
||||
};
|
||||
const requestJson = encodeRequest(requestData);
|
||||
|
||||
const response = await command.run(requestJson, {});
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe("`itemid` was not provided.");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||
import { AuthType } from "@bitwarden/common/tools/send/models/domain/send";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
|
||||
@@ -53,14 +54,30 @@ export class SendEditCommand {
|
||||
req.id = normalizedOptions.itemId || req.id;
|
||||
if (normalizedOptions.emails) {
|
||||
req.emails = normalizedOptions.emails;
|
||||
req.password = undefined;
|
||||
} else if (normalizedOptions.password) {
|
||||
req.emails = undefined;
|
||||
}
|
||||
if (normalizedOptions.password) {
|
||||
req.password = normalizedOptions.password;
|
||||
} else if (req.password && (typeof req.password !== "string" || req.password === "")) {
|
||||
}
|
||||
if (req.password && (typeof req.password !== "string" || req.password === "")) {
|
||||
req.password = undefined;
|
||||
}
|
||||
|
||||
// Infer authType based on emails/password (mutually exclusive)
|
||||
const hasEmails = req.emails != null && req.emails.length > 0;
|
||||
const hasPassword = req.password != null && req.password !== "";
|
||||
|
||||
if (hasEmails && hasPassword) {
|
||||
return Response.badRequest("--password and --emails are mutually exclusive.");
|
||||
}
|
||||
|
||||
if (hasEmails) {
|
||||
req.authType = AuthType.Email;
|
||||
} else if (hasPassword) {
|
||||
req.authType = AuthType.Password;
|
||||
} else {
|
||||
req.authType = AuthType.None;
|
||||
}
|
||||
|
||||
if (!req.id) {
|
||||
return Response.error("`itemid` was not provided.");
|
||||
}
|
||||
@@ -93,6 +110,8 @@ export class SendEditCommand {
|
||||
// Add dates from template
|
||||
encSend.deletionDate = sendView.deletionDate;
|
||||
encSend.expirationDate = sendView.expirationDate;
|
||||
encSend.emails = req.emails && req.emails.join(",");
|
||||
encSend.authType = req.authType;
|
||||
|
||||
await this.sendApiService.save([encSend, encFileData]);
|
||||
} catch (e) {
|
||||
|
||||
@@ -55,6 +55,7 @@ export class SendResponse implements BaseResponse {
|
||||
view.emails = send.emails ?? [];
|
||||
view.disabled = send.disabled;
|
||||
view.hideEmail = send.hideEmail;
|
||||
view.authType = send.authType;
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user