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

feat: ensure domain uniqueness #439

Merged
merged 9 commits into from
Sep 25, 2024
8 changes: 5 additions & 3 deletions src/helpers/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ export async function addOrUpdateSpace(space: string, settings: any) {

const ts = (Date.now() / 1e3).toFixed();
const query =
'INSERT INTO spaces SET ? ON DUPLICATE KEY UPDATE updated = ?, settings = ?, name = ?, hibernated = 0';
'INSERT INTO spaces SET ? ON DUPLICATE KEY UPDATE updated = ?, settings = ?, name = ?, hibernated = 0, domain = ?';

await db.queryAsync(query, [
{
id: space,
name: settings.name,
created: ts,
updated: ts,
settings: JSON.stringify(settings)
settings: JSON.stringify(settings),
domain: settings.domain || null
ChaituVR marked this conversation as resolved.
Show resolved Hide resolved
},
ts,
JSON.stringify(settings),
settings.name
settings.name,
settings.domain || null
]);
}

Expand Down
11 changes: 11 additions & 0 deletions src/writer/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ export async function verify(body): Promise<any> {
const isAdmin = admins.includes(body.address.toLowerCase());
const newAdmins = (msg.payload.admins || []).map(admin => admin.toLowerCase());

const anotherSpaceWithDomain = (
await db.queryAsync('SELECT 1 FROM spaces WHERE domain = ? AND id != ? LIMIT 1', [
msg.payload.domain,
msg.space
])
)[0];

if (msg.payload.domain && anotherSpaceWithDomain) {
return Promise.reject('domain already taken');
}

if (!isAdmin && !isController) return Promise.reject('not allowed');

if (!isController && !isEqual(admins, newAdmins))
Expand Down
131 changes: 131 additions & 0 deletions test/integration/writer/settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import db, { sequencerDB } from '../../../src/helpers/mysql';
import { action, verify } from '../../../src/writer/settings';
import payload from '../../fixtures/writer-payload/space.json';

const defaultSpace = JSON.parse(payload.msg).space;
const spaceId = 'test-domain-uniqueness.eth';
const testDomainId = 'test-domain-uniqueness-2.eth';

function getInput(id = defaultSpace, msgPayload = {}) {
const input = { ...payload };
const inputMsg = JSON.parse(input.msg);
inputMsg.payload = { ...inputMsg.payload, ...msgPayload };
inputMsg.space = id;

input.msg = JSON.stringify(inputMsg);

return input;
}

afterEach(() => {
jest.restoreAllMocks();
});

const mockGetSpaceController = jest.fn((): any => {
return '';
});
jest.mock('@snapshot-labs/snapshot.js', () => {
const originalModule = jest.requireActual('@snapshot-labs/snapshot.js');

return {
...originalModule,
utils: {
...originalModule.utils,
getSpaceController: () => mockGetSpaceController()
}
};
});

describe.skip('writer/settings', () => {
afterAll(async () => {
await db.queryAsync('DELETE FROM snapshot_sequencer_test.spaces WHERE id IN (?)', [
spaceId,
testDomainId
]);
await db.endAsync();
await sequencerDB.endAsync();
});

describe('verify()', () => {
const DOMAIN = 'vote.snapshot.org';

beforeAll(async () => {
await db.queryAsync(
'INSERT INTO snapshot_sequencer_test.spaces (id, name, created, updated, domain) VALUES (?, ?, 0, 0, ?)',
[testDomainId, testDomainId, DOMAIN]
);
});

afterAll(async () => {
await db.queryAsync('DELETE FROM snapshot_sequencer_test.spaces WHERE id = ?', [
testDomainId
]);
});

it('rejects when domain is used by another space', async () => {
const input = getInput(spaceId, { domain: DOMAIN });

mockGetSpaceController.mockResolvedValueOnce(input.address);
await expect(verify(input)).rejects.toContain('domain already taken');
expect(mockGetSpaceController).toHaveBeenCalledTimes(1);
});

it('resolves when domain is not used yet', async () => {
const input = getInput(spaceId, { domain: 'vote1.snapshot.org' });

mockGetSpaceController.mockResolvedValueOnce(input.address);
await expect(verify(input)).resolves.toBeUndefined();
expect(mockGetSpaceController).toHaveBeenCalledTimes(1);
});
});

describe('action()', () => {
afterEach(async () => {
await db.queryAsync('DELETE FROM snapshot_sequencer_test.spaces WHERE id IN (?)', [
spaceId,
testDomainId
]);
});

describe('with a domain', () => {
it('saves the domain in space settings and in the domain column', async () => {
const domain = 'test-domain.eth';
const input = getInput(spaceId, { domain });
const save = await action(input);

expect(save).resolves;

const results = (
await db.queryAsync(
"SELECT id, JSON_UNQUOTE(settings->'$.domain') as settingsDomain, domain as columnDomain FROM spaces WHERE id = ?",
[spaceId]
)
)[0];

expect(results.id).toBe(spaceId);
expect(results.settingsDomain).toBe(domain);
expect(results.columnDomain).toBe(domain);
});
});

describe('without domain', () => {
it('sets the domain in space setting and column as NULL', async () => {
const input = getInput(spaceId);
const save = await action(input);

expect(save).resolves;

const results = (
await db.queryAsync(
"SELECT id, JSON_UNQUOTE(settings->'$.domain') as settingsDomain, domain as columnDomain FROM spaces WHERE id = ?",
[spaceId]
)
)[0];

expect(results.id).toBe(spaceId);
expect(results.settingsDomain).toBe(null);
expect(results.columnDomain).toBe(null);
});
});
});
});
2 changes: 2 additions & 0 deletions test/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ CREATE TABLE spaces (
proposal_count INT NOT NULL DEFAULT '0',
vote_count INT NOT NULL DEFAULT '0',
follower_count INT NOT NULL DEFAULT '0',
domain VARCHAR(64) DEFAULT NULL,
created BIGINT NOT NULL,
updated BIGINT NOT NULL,
PRIMARY KEY (id),
INDEX name (name),
UNIQUE KEY domain (domain),
INDEX verified (verified),
INDEX flagged (flagged),
INDEX hibernated (hibernated),
Expand Down
Loading