Skip to content
Open
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 Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
test:
cd packages/api-main && docker compose down
cd packages/api-main && docker compose up -d
sleep 5
cd packages/api-main && PG_URI="postgresql://default:password@localhost:5432/postgres" pnpm db:push:force || true
cd packages/api-main && NODE_OPTIONS="--experimental-global-webcrypto" pnpm test
cd packages/api-main && docker compose down
2 changes: 1 addition & 1 deletion packages/api-main/src/gets/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const statement = getDatabase()
and(
isNull(FeedTable.removed_at),
isNull(FeedTable.post_hash),
gte(FeedTable.quantity, sql.placeholder('minQuantity')),
gte(sql`CAST(${FeedTable.quantity} AS NUMERIC)`, sql`CAST(${sql.placeholder('minQuantity')} AS NUMERIC)`),
),
)
.orderBy(desc(FeedTable.timestamp))
Expand Down
4 changes: 2 additions & 2 deletions packages/api-main/src/gets/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const statement = getDatabase()
eq(FeedTable.author, sql.placeholder('author')),
isNull(FeedTable.removed_at),
isNull(FeedTable.post_hash), // Do not return replies
gte(FeedTable.quantity, sql.placeholder('minQuantity')),
gte(sql`CAST(${FeedTable.quantity} AS NUMERIC)`, sql`CAST(${sql.placeholder('minQuantity')} AS NUMERIC)`),
))
.limit(sql.placeholder('limit'))
.offset(sql.placeholder('offset'))
Expand Down Expand Up @@ -57,7 +57,7 @@ const followingPostsStatement = getDatabase()
),
isNull(FeedTable.post_hash),
isNull(FeedTable.removed_at),
gte(FeedTable.quantity, sql.placeholder('minQuantity')),
gte(sql`CAST(${FeedTable.quantity} AS NUMERIC)`, sql`CAST(${sql.placeholder('minQuantity')} AS NUMERIC)`),
))
.orderBy(desc(FeedTable.timestamp))
.limit(sql.placeholder('limit'))
Expand Down
4 changes: 2 additions & 2 deletions packages/api-main/src/gets/replies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const statement = getDatabase()
.where(and(
eq(FeedTable.post_hash, sql.placeholder('hash')),
isNull(FeedTable.removed_at),
gte(FeedTable.quantity, sql.placeholder('minQuantity')),
gte(sql`CAST(${FeedTable.quantity} AS NUMERIC)`, sql`CAST(${sql.placeholder('minQuantity')} AS NUMERIC)`),
))
.limit(sql.placeholder('limit'))
.offset(sql.placeholder('offset'))
Expand Down Expand Up @@ -58,7 +58,7 @@ const getUserRepliesWithParent = getDatabase()
.where(and(
eq(feed.author, sql.placeholder('author')),
isNotNull(feed.post_hash),
gte(feed.quantity, sql.placeholder('minQuantity')),
gte(sql`CAST(${feed.quantity} AS NUMERIC)`, sql`CAST(${sql.placeholder('minQuantity')} AS NUMERIC)`),
))
.orderBy(desc(feed.timestamp))
.limit(sql.placeholder('limit'))
Expand Down
2 changes: 1 addition & 1 deletion packages/api-main/src/gets/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function Search(query: typeof Gets.SearchQuery.static) {
sql`to_tsvector('english', ${FeedTable.message}) @@ to_tsquery('english', ${processedQuery})`,
inArray(FeedTable.author, matchedAuthorAddresses),
),
gte(FeedTable.quantity, minQuantity),
gte(sql`CAST(${FeedTable.quantity} AS NUMERIC)`, sql`CAST(${minQuantity} AS NUMERIC)`),
isNull(FeedTable.removed_at),
),
)
Expand Down
48 changes: 47 additions & 1 deletion packages/api-main/tests/feed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,53 @@ describe('filter post depending on send tokens', async () => {
}[];
}>(`feed?minQuantity=${expensivePostTokens}`);
assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`);
assert.lengthOf(readResponse.rows, 1);
// Should include the expensive post we created
const hasExpensivePost = readResponse.rows.some(row => row.message === expensivePostMessage);
assert.isTrue(hasExpensivePost, 'Should include the expensive post');
// Should not include the cheap post
const hasCheapPost = readResponse.rows.some(row => row.message === cheapPostMessage);
assert.isFalse(hasCheapPost, 'Should not include the cheap post');
});

it('filtering with large numbers (Photon filter bug fix)', async () => {
// Create posts with amounts that would fail with string comparison
const wallet = await createWallet();

// Post with 500000 tokens
const post500k = await post(`post`, {
from: wallet.publicKey,
hash: getRandomHash(),
msg: 'post with 500000 tokens',
quantity: '500000',
timestamp: '2025-04-16T19:46:42Z',
} as typeof Posts.PostBody.static);
assert.isOk(post500k?.status === 200);

// Post with 50000 tokens
const post50k = await post(`post`, {
from: wallet.publicKey,
hash: getRandomHash(),
msg: 'post with 50000 tokens',
quantity: '50000',
timestamp: '2025-04-16T19:46:43Z',
} as typeof Posts.PostBody.static);
assert.isOk(post50k?.status === 200);

// Filter for posts >= 100000
// String comparison: "500000" > "100000" but "50000" < "100000" (WRONG - "50000" > "100000")
// Numeric comparison: 500000 > 100000 and 50000 < 100000 (CORRECT)
const filterResponse = await get<{
status: number;
rows: {
message: string;
quantity: string;
}[];
}>(`feed?minQuantity=100000`);

assert.isOk(filterResponse?.status === 200);
const filteredMessages = filterResponse.rows.map(r => r.message);
assert.include(filteredMessages, 'post with 500000 tokens', 'Should include 500k post');
assert.notInclude(filteredMessages, 'post with 50000 tokens', 'Should NOT include 50k post');
});

it('Search: filtering cheap posts', async () => {
Expand Down
20 changes: 11 additions & 9 deletions packages/api-main/tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@ import { start } from '../src/index';

async function clearTables() {
console.log('Clearing Tables');
for (const tableName of tables) {
await getDatabase().execute(sql`TRUNCATE TABLE ${sql.raw(tableName)};`);
}
}

export async function setup(project: TestProject) {
try {
stop();
for (const tableName of tables) {
await getDatabase().execute(sql`TRUNCATE TABLE ${sql.raw(tableName)};`);
}
}
catch (_err) {
console.log(`Skipping Stop Step`);
catch (err) {
console.error('Error clearing tables:', err);
// Continue anyway - tables might not exist yet
}
}

export async function setup(project: TestProject) {
start();

// Give server time to start
await new Promise(resolve => setTimeout(resolve, 1000));

project.onTestsRerun(clearTables);
await clearTables();
}
31 changes: 30 additions & 1 deletion packages/api-main/tests/v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Posts } from '@atomone/dither-api-types';

import { assert, describe, it } from 'vitest';

import { createPost, get, getAtomOneAddress, getRandomHash, post } from './shared';
import { createPost, createWallet, get, getAtomOneAddress, getRandomHash, post } from './shared';

describe('v1', { sequential: true }, async () => {
const addressUserA = getAtomOneAddress();
Expand Down Expand Up @@ -72,6 +72,35 @@ describe('v1', { sequential: true }, async () => {
);
});

it('GET - /posts with minQuantity filter (Photon bug fix)', async () => {
const wallet = await createWallet();

// Create posts with different quantities
await post(`post`, {
from: wallet.publicKey,
hash: getRandomHash(),
msg: 'low value post',
quantity: '100',
timestamp: '2025-04-16T19:46:42Z',
} as typeof Posts.PostBody.static);

await post(`post`, {
from: wallet.publicKey,
hash: getRandomHash(),
msg: 'high value post',
quantity: '1000000',
timestamp: '2025-04-16T19:46:43Z',
} as typeof Posts.PostBody.static);

// Test numeric comparison works correctly
const response = await get<{ status: number; rows: { message: string; quantity: string }[] }>(
`posts?address=${wallet.publicKey}&minQuantity=500000`,
);
assert.isOk(response?.status === 200);
assert.lengthOf(response.rows, 1);
assert.equal(response.rows[0].message, 'high value post');
});

// Likes
const likeablePost = await createPost('A Likeable Post');
it ('should have a likeable post', () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/api-main/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ export default defineConfig({
forceRerunTriggers: [
'**/tests/**/*',
],
pool: 'forks',
poolOptions: {
forks: {
singleFork: true,
},
},
// reporters: ['verbose'],
},
define: {
Expand Down
Loading