Refactor topic feed: reduce CRAP, DRY, and add property coverage

- Add topics controller error-handling unit tests to bring handleError
  CRAP from 14.7 to 4.0.
- Consolidate duplicate getTopicPosts/getPostsByAddr HTTP page fetches
  into a shared getPage helper in memo-db (DRY).
- Add TopicQuery property tests: listTopics conservation/ordering,
  getTopicPostTxids newest-first pagination, and room/txid key round-trips.
- Add client topic-services property tests for getTopic/getPost lookup
  completeness.

By refactorer.
This commit is contained in:
Chris Troutner
2026-08-28 06:43:18 -07:00
parent 30dcf244cf
commit 4c5fb38661
4 changed files with 284 additions and 23 deletions
+18 -23
View File
@@ -52,17 +52,8 @@ class MemoDb {
return this.getRecent('/topics', 'getTopics', {})
}
async getTopicPosts (room, { limit = 100, offset = 0 } = {}) {
try {
const result = await this.axios.get(
`${config.backend}/topics/${encodeURIComponent(room)}/posts`,
{ params: { limit, offset } }
)
return result.data
} catch (err) {
console.error('Error in getTopicPosts()')
throw err
}
async getTopicPosts (room, opts = {}) {
return this.getPage(`/topics/${encodeURIComponent(room)}/posts`, 'getTopicPosts', opts)
}
// GET a paginated 'recent' listing endpoint.
@@ -79,6 +70,20 @@ class MemoDb {
}
}
// GET a paginated resource page at a full path.
async getPage (path, name, { limit = 100, offset = 0 } = {}) {
try {
const result = await this.axios.get(`${config.backend}${path}`, {
params: { limit, offset }
})
return result.data
} catch (err) {
console.error(`Error in ${name}()`)
throw err
}
}
// GET a level endpoint that resolves an address. Returns null on 404.
async getLevelResource (endpoint, addr, name) {
try {
@@ -95,18 +100,8 @@ class MemoDb {
}
}
async getPostsByAddr (addr, { limit = 100, offset = 0 } = {}) {
try {
const result = await this.axios.get(
`${config.backend}/posts/by/${encodeURIComponent(addr)}`,
{ params: { limit, offset } }
)
return result.data
} catch (err) {
console.error('Error in getPostsByAddr()')
throw err
}
async getPostsByAddr (addr, opts = {}) {
return this.getPage(`/posts/by/${encodeURIComponent(addr)}`, 'getPostsByAddr', opts)
}
async getPostThread (txid) {
@@ -0,0 +1,80 @@
/*
Property tests for the topic discovery and topic feed page controllers.
The unit tests probe getTopic / getPost at a few fixed fixtures. These
properties pin down the lookup invariants over broad random inputs:
- TopicDiscoveryPage.getTopic finds any topic that was loaded.
- TopicFeedPage.getPost finds any post that was loaded.
- Lookups are stable: a loaded item is always found, and an unknown key
returns null.
*/
'use strict'
const test = require('node:test')
const { seededRandom, forAll, intGen } = require('./harness')
const TopicDiscoveryPage = require('../../src/services/topic-discovery-page')
const TopicFeedPage = require('../../src/services/topic-feed-page')
const rng = seededRandom(20260828)
function randomRoom () {
return 'room-' + Math.floor(rng() * 1e6)
}
function randomTxid () {
return 't' + rng().toString(36).slice(2, 10) + Math.floor(rng() * 1e6)
}
test('TopicDiscoveryPage.getTopic finds any loaded topic', async () => {
await forAll(
(i) => {
const n = intGen(rng, 0, 20)()
const topics = []
for (let j = 0; j < n; j++) {
topics.push({ room: randomRoom(), postCount: intGen(rng, 0, 1000)() })
}
return topics
},
async (topics) => {
const memoDb = { async getTopics () { return { topics } } }
const page = new TopicDiscoveryPage({ memoDb })
await page.load()
for (const topic of topics) {
const found = page.getTopic(topic.room)
if (!found || found.room !== topic.room) return false
}
if (page.getTopic('does-not-exist') !== null) return false
return true
},
{ label: 'topic discovery lookup completeness' }
)
})
test('TopicFeedPage.getPost finds any loaded post', async () => {
await forAll(
(i) => {
const n = intGen(rng, 0, 20)()
const posts = []
for (let j = 0; j < n; j++) {
posts.push({ txid: randomTxid(), text: 'post ' + j })
}
return posts
},
async (posts) => {
const memoDb = { async getTopicPosts () { return { posts, pagination: { total: posts.length } } } }
const page = new TopicFeedPage({ memoDb, room: 'bitcoin' })
await page.load()
for (const post of posts) {
const found = page.getPost(post.txid)
if (!found || found.txid !== post.txid) return false
}
if (page.getPost('does-not-exist') !== null) return false
return true
},
{ label: 'topic feed lookup completeness' }
)
})