Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
19 changes: 10 additions & 9 deletions packages/api-main/tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ 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)};`);
try {
for (const tableName of tables) {
await getDatabase().execute(sql`TRUNCATE TABLE ${sql.raw(tableName)};`);
}
} catch (err) {
console.error('Error clearing tables:', err);
// Continue anyway - tables might not exist yet
}
}

export async function setup(project: TestProject) {
try {
stop();
}
catch (_err) {
console.log(`Skipping Stop Step`);
}

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