Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement AddressBookItem update #2149

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ describe('AddressBooksDataSource', () => {
const addressBook = await target.getAddressBook({ account, chainId });
const updatedItem = await target.updateAddressBookItem({
addressBook,
updateAddressBookItem: updatedAddressBookItem,
updateAddressBookItemDto: updatedAddressBookItem,
});

expect(updatedItem).toMatchObject(updatedAddressBookItem);
Expand Down Expand Up @@ -219,7 +219,7 @@ describe('AddressBooksDataSource', () => {
const addressBook = await target.getAddressBook({ account, chainId });
const updatedItem = await target.updateAddressBookItem({
addressBook,
updateAddressBookItem: updatedAddressBookItem,
updateAddressBookItemDto: updatedAddressBookItem,
});

expect(updatedItem).toMatchObject(updatedAddressBookItem);
Expand Down Expand Up @@ -251,7 +251,7 @@ describe('AddressBooksDataSource', () => {
await expect(
target.updateAddressBookItem({
addressBook,
updateAddressBookItem: updateAddressBookItemDtoBuilder()
updateAddressBookItemDto: updateAddressBookItemDtoBuilder()
.with('id', nonExistentId)
.build(),
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
AddressBookItem,
} from '@/domain/accounts/address-books/entities/address-book.entity';
import { CreateAddressBookItemDto } from '@/domain/accounts/address-books/entities/create-address-book-item.dto.entity';
import { UpdateAddressBookItemDto } from '@/domain/accounts/address-books/entities/update-address-book.item.dto.entity';
import { UpdateAddressBookItemDto } from '@/domain/accounts/address-books/entities/update-address-book-item.dto.entity';
import { AddressBookItemNotFoundError } from '@/domain/accounts/address-books/errors/address-book-item-not-found.error';
import { AddressBookNotFoundError } from '@/domain/accounts/address-books/errors/address-book-not-found.error';
import { Account } from '@/domain/accounts/entities/account.entity';
Expand Down Expand Up @@ -56,15 +56,15 @@ export class AddressBooksDatasource implements IAddressBooksDatasource {

async updateAddressBookItem(args: {
addressBook: AddressBook;
PooyaRaki marked this conversation as resolved.
Show resolved Hide resolved
updateAddressBookItem: UpdateAddressBookItemDto;
updateAddressBookItemDto: UpdateAddressBookItemDto;
}): Promise<AddressBookItem> {
const { addressBook, updateAddressBookItem } = args;
const { addressBook, updateAddressBookItemDto } = args;
const addressBookItem = addressBook.data.find(
(i) => i.id === updateAddressBookItem.id,
(i) => i.id === updateAddressBookItemDto.id,
);
if (!addressBookItem) throw new AddressBookItemNotFoundError();
addressBookItem.address = updateAddressBookItem.address;
addressBookItem.name = updateAddressBookItem.name;
addressBookItem.address = updateAddressBookItemDto.address;
addressBookItem.name = updateAddressBookItemDto.name;
await this.updateAddressBook(addressBook);
return addressBookItem;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
AddressBookItem,
} from '@/domain/accounts/address-books/entities/address-book.entity';
import { CreateAddressBookItemDto } from '@/domain/accounts/address-books/entities/create-address-book-item.dto.entity';
import { UpdateAddressBookItemDto } from '@/domain/accounts/address-books/entities/update-address-book-item.dto.entity';
import { AuthPayload } from '@/domain/auth/entities/auth-payload.entity';
import { Module } from '@nestjs/common';

Expand All @@ -25,6 +26,13 @@ export interface IAddressBooksRepository {
createAddressBookItemDto: CreateAddressBookItemDto;
}): Promise<AddressBookItem>;

updateAddressBookItem(args: {
authPayload: AuthPayload;
address: `0x${string}`;
chainId: string;
updateAddressBookItemDto: UpdateAddressBookItemDto;
}): Promise<AddressBookItem>;

deleteAddressBook(args: {
authPayload: AuthPayload;
address: `0x${string}`;
Expand Down
35 changes: 35 additions & 0 deletions src/domain/accounts/address-books/address-books.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
AddressBookItem,
} from '@/domain/accounts/address-books/entities/address-book.entity';
import { CreateAddressBookItemDto } from '@/domain/accounts/address-books/entities/create-address-book-item.dto.entity';
import { UpdateAddressBookItemDto } from '@/domain/accounts/address-books/entities/update-address-book-item.dto.entity';
import {
AccountDataType,
AccountDataTypeNames,
Expand Down Expand Up @@ -86,6 +87,40 @@ export class AddressBooksRepository implements IAddressBooksRepository {
});
}

/**
* Updates an AddressBookItem.
* Checks that the account has the AddressBooks Data Setting enabled.
*
* If an AddressBook for the Account and chainId does not exist,
* an AddressBookNotFound error is thrown.
*/
async updateAddressBookItem(args: {
authPayload: AuthPayload;
address: `0x${string}`;
chainId: string;
updateAddressBookItemDto: UpdateAddressBookItemDto;
}): Promise<AddressBookItem> {
if (!args.authPayload.isForSigner(args.address)) {
throw new UnauthorizedException();
PooyaRaki marked this conversation as resolved.
Show resolved Hide resolved
}
await this.checkAddressBooksIsEnabled({
authPayload: args.authPayload,
address: args.address,
});
const account = await this.accountsRepository.getAccount({
authPayload: args.authPayload,
address: args.address,
});
const addressBook = await this.datasource.getAddressBook({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Does it happen that the account is not found? If so, we shouldn't try to fetch the address book and update.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will throw here if there is no account so it won't try to fetch/update the addressbook

account,
chainId: args.chainId,
});
return this.datasource.updateAddressBookItem({
addressBook,
updateAddressBookItemDto: args.updateAddressBookItemDto,
});
}

async deleteAddressBook(args: {
authPayload: AuthPayload;
address: `0x${string}`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Builder, type IBuilder } from '@/__tests__/builder';
import type { UpdateAddressBookItemDto } from '@/domain/accounts/address-books/entities/update-address-book.item.dto.entity';
import type { UpdateAddressBookItemDto } from '@/domain/accounts/address-books/entities/update-address-book-item.dto.entity';
import { accountNameBuilder } from '@/domain/accounts/entities/__tests__/account-name.builder';
import { DB_MAX_SAFE_INTEGER } from '@/domain/common/constants';
import { faker } from '@faker-js/faker/.';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,61 +107,6 @@ describe('AddressBookSchema', () => {
]);
});

it('should not verify an AddressBookItem with a shorter name', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why are these tests deleted?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as #2149 (comment)

const addressBook = addressBookBuilder().build();
addressBook.data[0].name = 'e'; // min length is 3

const result = AddressBookSchema.safeParse(addressBook);

expect(!result.success && result.error.issues).toStrictEqual([
{
code: 'too_small',
exact: false,
inclusive: true,
message: 'Address book entry names must be at least 3 characters long',
minimum: 3,
path: ['data', 0, 'name'],
type: 'string',
},
]);
});

it('should not verify an AddressBookItem with a longer name', () => {
const addressBook = addressBookBuilder().build();
addressBook.data[0].name = 'e'.repeat(51); // max length is 50

const result = AddressBookSchema.safeParse(addressBook);

expect(!result.success && result.error.issues).toStrictEqual([
{
code: 'too_big',
exact: false,
inclusive: true,
message: 'Address book entry names must be at most 50 characters long',
maximum: 50,
path: ['data', 0, 'name'],
type: 'string',
},
]);
});

it('should not verify an AddressBookItem with a malformed name', () => {
const addressBook = addressBookBuilder().build();
addressBook.data[0].name = '////'; // must start with a letter or number

const result = AddressBookSchema.safeParse(addressBook);

expect(!result.success && result.error.issues).toStrictEqual([
{
code: 'invalid_string',
message:
'Address book entry names must start with a letter or number and can contain alphanumeric characters, periods, underscores, or hyphens',
path: ['data', 0, 'name'],
validation: 'regex',
},
]);
});

it('should not verify an AddressBookItem with a malformed address', () => {
const addressBook = addressBookBuilder().build();
addressBook.data[0].address = '0x123';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createAddressBookItemDtoBuilder } from '@/domain/accounts/address-books/entities/__tests__/create-address-book-item.dto.builder';
import { CreateAddressBookItemDtoSchema } from '@/domain/accounts/address-books/entities/create-address-book-item.dto.entity';
import { faker } from '@faker-js/faker/.';
import { getAddress } from 'viem';

describe('CreateAddressBookItemDtoSchema', () => {
Expand All @@ -14,91 +13,6 @@ describe('CreateAddressBookItemDtoSchema', () => {
expect(result.success).toBe(true);
});

it('should not verify a CreateAddressBookItemDto with a shorter name', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are CreateAddressBookItemDto tests deleted?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a comment in a previous PR: #2151 (comment)

@iamacook suggested to just keep the tests for address book names in the address book name schema, so as not to test the same cases multiple times. I didn't remove them in that PR so removing them here.

const createAddressBookItemDto = createAddressBookItemDtoBuilder()
.with('name', faker.string.alphanumeric({ length: 2 }))
.build();

const result = CreateAddressBookItemDtoSchema.safeParse(
createAddressBookItemDto,
);

expect(!result.success && result.error.issues).toStrictEqual([
{
code: 'too_small',
inclusive: true,
exact: false,
message: 'Address book entry names must be at least 3 characters long',
minimum: 3,
path: ['name'],
type: 'string',
},
]);
});

it('should not verify a CreateAddressBookItemDto with a number name', () => {
const createAddressBookItemDto = createAddressBookItemDtoBuilder()
// @ts-expect-error - should be strings
.with('name', faker.number.int())
.build();

const result = CreateAddressBookItemDtoSchema.safeParse(
createAddressBookItemDto,
);

expect(!result.success && result.error.issues).toStrictEqual([
{
code: 'invalid_type',
expected: 'string',
message: 'Expected string, received number',
path: ['name'],
received: 'number',
},
]);
});

it('should not verify a CreateAddressBookItemDto with a longer name', () => {
const createAddressBookItemDto = createAddressBookItemDtoBuilder()
.with('name', faker.string.alphanumeric({ length: 51 }))
.build();

const result = CreateAddressBookItemDtoSchema.safeParse(
createAddressBookItemDto,
);

expect(!result.success && result.error.issues).toStrictEqual([
{
code: 'too_big',
inclusive: true,
exact: false,
message: 'Address book entry names must be at most 50 characters long',
maximum: 50,
path: ['name'],
type: 'string',
},
]);
});

it('should not verify a CreateAddressBookItemDto with a malformed name', () => {
const createAddressBookItemDto = createAddressBookItemDtoBuilder()
.with('name', '////')
.build();

const result = CreateAddressBookItemDtoSchema.safeParse(
createAddressBookItemDto,
);

expect(!result.success && result.error.issues).toStrictEqual([
{
code: 'invalid_string',
message:
'Address book entry names must start with a letter or number and can contain alphanumeric characters, periods, underscores, or hyphens',
path: ['name'],
validation: 'regex',
},
]);
});

it('should not verify a CreateAddressBookItemDto with a malformed address', () => {
const createAddressBookItemDto = createAddressBookItemDtoBuilder()
.with('address', '0x123')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { updateAddressBookItemDtoBuilder } from '@/domain/accounts/address-books/entities/__tests__/update-address-book-item.dto.builder';
import { UpdateAddressBookItemDtoSchema } from '@/domain/accounts/address-books/entities/update-address-book-item.dto.entity';
import { faker } from '@faker-js/faker/.';
import { getAddress } from 'viem';

describe('UpdateAddressBookItemDtoSchema', () => {
it('should verify a UpdateAddressBookItemDto', () => {
const updateAddressBookItemDto = updateAddressBookItemDtoBuilder().build();

const result = UpdateAddressBookItemDtoSchema.safeParse(
updateAddressBookItemDto,
);

expect(result.success).toBe(true);
});

it('should not verify an UpdateAddressBookItemDto with a string id', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that we are testing Zod validator, right?

const updateAddressBookItemDto = updateAddressBookItemDtoBuilder()
// @ts-expect-error - should be numbers
.with('id', faker.string.alphanumeric())
.build();

const result = UpdateAddressBookItemDtoSchema.safeParse(
updateAddressBookItemDto,
);

expect(!result.success && result.error.issues).toStrictEqual([
{
code: 'invalid_type',
expected: 'number',
message: 'Expected number, received string',
path: ['id'],
received: 'string',
},
]);
});

it('should not verify an UpdateAddressBookItemDto with a malformed address', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well, it seems like we are testing the external validator library.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep this as it's important to ensure addresses come in correct.

const updateAddressBookItemDto = updateAddressBookItemDtoBuilder()
.with('address', '0x123')
.build();

const result = UpdateAddressBookItemDtoSchema.safeParse(
updateAddressBookItemDto,
);

expect(!result.success && result.error.issues).toStrictEqual([
{
code: 'custom',
message: 'Invalid address',
path: ['address'],
},
]);
});

it('should checksum the address of an UpdateAddressBookItemDto', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the previous comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep this as it's important to ensure addresses come in checksummed.

const updateAddressBookItemDto = updateAddressBookItemDtoBuilder().build();
// @ts-expect-error - address should be `0x${string}`
updateAddressBookItemDto.address =
updateAddressBookItemDto.address.toLowerCase();

const result = UpdateAddressBookItemDtoSchema.safeParse(
updateAddressBookItemDto,
);

expect(result.success && result.data.address).toBe(
getAddress(updateAddressBookItemDto.address),
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AddressBookSchema } from '@/domain/accounts/address-books/entities/address-book.entity';
import { AddressBookItemNameSchema } from '@/domain/accounts/address-books/entities/schemas/address-book-item-name.schema';
import { AddressSchema } from '@/validation/entities/schemas/address.schema';
import { z } from 'zod';

export const UpdateAddressBookItemDtoSchema = z.object({
id: AddressBookSchema.shape.id,
name: AddressBookItemNameSchema,
address: AddressSchema,
});

export class UpdateAddressBookItemDto
implements z.infer<typeof UpdateAddressBookItemDtoSchema>
{
id: number;
name: string;
address: `0x${string}`;

constructor(props: UpdateAddressBookItemDto) {
this.id = props.id;
this.name = props.name;
this.address = props.address;
}
}
Loading
Loading