-
Notifications
You must be signed in to change notification settings - Fork 39
Add DB-backed test coverage and run Postgres tests in CI #249
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
Merged
Merged
Changes from 75 commits
Commits
Show all changes
76 commits
Select commit
Hold shift + click to select a range
d10c1da
Extract shared Postgres test helpers
dahlia 4cebc21
Add Postgres tests for following state
dahlia 7bd58bb
Add Postgres tests for reaction lifecycle
dahlia 6bd6d9f
Add GraphQL notification ordering test
dahlia d427530
Add Postgres tests for share lifecycle
dahlia 5028202
Fix deletePost counter updates
dahlia 6ec4a84
Add GraphQL tests for post mutations
dahlia 2ae35d1
Add Postgres tests for timeline fanout
dahlia 335ca61
Add Postgres tests for timeline queries
dahlia 8274e5f
Add GraphQL tests for timeline queries
dahlia d602ff3
Add GraphQL tests for actor mutations
dahlia 957c94a
Add stateful auth test helpers
dahlia f0c99a6
Add GraphQL tests for signup flow
dahlia e48d01b
Add GraphQL tests for login flow
dahlia 5c5098e
Add GraphQL tests for passkey flows
dahlia 538afb1
Fix local block relationship cleanup
dahlia a3ba9a5
Add GraphQL tests for invitation links
dahlia 0a2a7f9
Fix invite validation and cleanup
dahlia d83baab
Fix notification merging in transactions
dahlia 5828807
Add signup model tests
dahlia 59fb7da
Add signin model tests
dahlia bff8587
Add passkey model tests
dahlia a130b26
Add session model tests
dahlia b50d782
Add actor model tests
dahlia 355814e
Add notification query tests
dahlia 8ec5aa9
Add account model DB tests
dahlia d8c7230
Add GraphQL account query tests
dahlia 7781ac5
Add GraphQL search tests
dahlia d8d4be8
Add note source model tests
dahlia 5068e76
Add note lifecycle tests
dahlia 7602536
Add poll model vote tests
dahlia 36386ea
Add GraphQL poll tests
dahlia ca2db17
Add article draft model tests
dahlia fd106cb
Add article source model tests
dahlia cb31166
Add article lifecycle model tests
dahlia e448bf8
Add GraphQL lookup helper tests
dahlia 42ac7f8
Add GraphQL account mutation tests
dahlia 3cc16e8
Add GraphQL misc tests
dahlia 5bbef36
Add GraphQL document tests
dahlia ad87108
Add GraphQL webfinger tests
dahlia b8379b8
Extend poll model tests
dahlia d3be91e
Add more GraphQL post tests
dahlia 220f7b6
Add remote post helper tests
dahlia b177023
Add post sync helper tests
dahlia 1d265cc
Add actor model persistence tests
dahlia 5eb0d64
Add more GraphQL actor tests
dahlia 02a4ca3
Add account helper model tests
dahlia 164dc15
Add APNS model tests
dahlia 8e95377
Add GraphQL APNS tests
dahlia ba4da8f
Add article background tests
dahlia d2bf92c
Add GraphQL searchPost tests
dahlia f00055e
Add post medium model tests
dahlia 67490d6
Add markup model tests
dahlia 6f2d0a4
Add more GraphQL passkey tests
dahlia fb4f028
Add login error path tests
dahlia bf5ca79
Add signup failure-path tests
dahlia 42ba1de
Add remote relationship model tests
dahlia 6c7c5fe
Set up PostgreSQL in CI
dahlia 061e29f
Fix blocking cleanup and invite refunds
dahlia 86cc16a
Stabilize tests flagged in review
dahlia 383ce2e
Fix duplicate follow notification handling
dahlia f295ab7
Clean up shared test helpers
dahlia c0afadb
Polish tests from review feedback
dahlia 7f96178
Make poll tests time-independent
dahlia 3bb2e3c
Preserve notification ordering on merges
dahlia 446279c
Fix notification merge timestamp casting
dahlia 950aabb
Avoid no-op notification merge side effects
dahlia f31c6cb
Isolate parallel test fixtures
dahlia 4b9ae89
Keep notification merges properly scoped
dahlia c93173f
Clean up test fixtures and helpers
dahlia b256f8e
Clean up remote follows on block
dahlia 78e56a0
Stub writable test disk methods
dahlia 1d3eb11
Simplify invite validation guards
dahlia 626090d
Retry notification merges after insert races
dahlia c03fbfe
Skip empty original-post lookups
dahlia 65c932f
Avoid notification insert race aborts
dahlia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,285 @@ | ||
| import assert from "node:assert/strict"; | ||
| import test from "node:test"; | ||
|
dahlia marked this conversation as resolved.
dahlia marked this conversation as resolved.
|
||
| import { encodeGlobalID } from "@pothos/plugin-relay"; | ||
| import * as vocab from "@fedify/vocab"; | ||
| import { execute, parse } from "graphql"; | ||
| import { updateAccountData } from "@hackerspub/models/account"; | ||
| import { schema } from "./mod.ts"; | ||
| import { | ||
| createFedCtx, | ||
| insertAccountWithActor, | ||
| makeGuestContext, | ||
| makeUserContext, | ||
| toPlainJson, | ||
| withRollback, | ||
| } from "../test/postgres.ts"; | ||
|
|
||
| const viewerQuery = parse(` | ||
| query Viewer { | ||
| viewer { | ||
| username | ||
| name | ||
| handle | ||
| } | ||
| } | ||
| `); | ||
|
|
||
| const accountByUsernameQuery = parse(` | ||
| query AccountByUsername($username: String!) { | ||
| accountByUsername(username: $username) { | ||
| username | ||
| name | ||
| handle | ||
| } | ||
| } | ||
| `); | ||
|
|
||
| const invitationTreeQuery = parse(` | ||
| query InvitationTree { | ||
| invitationTree { | ||
| id | ||
| username | ||
| name | ||
| avatarUrl | ||
| inviterId | ||
| hidden | ||
| } | ||
| } | ||
| `); | ||
|
|
||
| const updateAccountMutation = parse(` | ||
| mutation UpdateAccount($input: UpdateAccountInput!) { | ||
| updateAccount(input: $input) { | ||
| account { | ||
| username | ||
| bio | ||
| locales | ||
| preferAiSummary | ||
| defaultNoteVisibility | ||
| defaultShareVisibility | ||
| } | ||
| } | ||
| } | ||
| `); | ||
|
|
||
| test("viewer returns the signed-in account and null for guests", async () => { | ||
| await withRollback(async (tx) => { | ||
| const account = await insertAccountWithActor(tx, { | ||
| username: "viewerquery", | ||
| name: "Viewer Query", | ||
| email: "viewerquery@example.com", | ||
| }); | ||
|
|
||
| const signedInResult = await execute({ | ||
| schema, | ||
| document: viewerQuery, | ||
| contextValue: makeUserContext(tx, account.account), | ||
| onError: "NO_PROPAGATE", | ||
| }); | ||
|
|
||
| assert.equal(signedInResult.errors, undefined); | ||
| assert.deepEqual( | ||
| toPlainJson(signedInResult.data), | ||
| { | ||
| viewer: { | ||
| username: "viewerquery", | ||
| name: "Viewer Query", | ||
| handle: "@viewerquery@localhost", | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| const guestResult = await execute({ | ||
| schema, | ||
| document: viewerQuery, | ||
| contextValue: makeGuestContext(tx), | ||
| onError: "NO_PROPAGATE", | ||
| }); | ||
|
|
||
| assert.equal(guestResult.errors, undefined); | ||
| assert.deepEqual(toPlainJson(guestResult.data), { viewer: null }); | ||
| }); | ||
| }); | ||
|
|
||
| test("accountByUsername returns a local account by username", async () => { | ||
| await withRollback(async (tx) => { | ||
| const account = await insertAccountWithActor(tx, { | ||
| username: "lookupgraphql", | ||
| name: "Lookup GraphQL", | ||
| email: "lookupgraphql@example.com", | ||
| }); | ||
|
|
||
| const result = await execute({ | ||
| schema, | ||
| document: accountByUsernameQuery, | ||
| variableValues: { username: account.account.username }, | ||
| contextValue: makeGuestContext(tx), | ||
| onError: "NO_PROPAGATE", | ||
| }); | ||
|
|
||
| assert.equal(result.errors, undefined); | ||
| assert.deepEqual(toPlainJson(result.data), { | ||
| accountByUsername: { | ||
| username: "lookupgraphql", | ||
| name: "Lookup GraphQL", | ||
| handle: "@lookupgraphql@localhost", | ||
| }, | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| test("invitationTree redacts hidden accounts", async () => { | ||
| await withRollback(async (tx) => { | ||
| const visible = await insertAccountWithActor(tx, { | ||
| username: "visibletree", | ||
| name: "Visible Tree", | ||
| email: "visibletree@example.com", | ||
| }); | ||
| const hidden = await insertAccountWithActor(tx, { | ||
| username: "hiddentree", | ||
| name: "Hidden Tree", | ||
| email: "hiddentree@example.com", | ||
| }); | ||
|
|
||
| const updated = await updateAccountData(tx, { | ||
| id: hidden.account.id, | ||
| hideFromInvitationTree: true, | ||
| }); | ||
| assert.ok(updated != null); | ||
|
|
||
| const result = await execute({ | ||
| schema, | ||
| document: invitationTreeQuery, | ||
| contextValue: makeGuestContext(tx), | ||
| onError: "NO_PROPAGATE", | ||
| }); | ||
|
|
||
| assert.equal(result.errors, undefined); | ||
|
|
||
| const nodes = (result.data as { | ||
| invitationTree: Array<{ | ||
| id: string; | ||
| username: string | null; | ||
| name: string | null; | ||
| avatarUrl: string; | ||
| inviterId: string | null; | ||
| hidden: boolean; | ||
| }>; | ||
| }).invitationTree; | ||
| const visibleNode = nodes.find((node) => node.id === visible.account.id); | ||
| const hiddenNode = nodes.find((node) => node.id === hidden.account.id); | ||
|
|
||
| assert.ok(visibleNode != null); | ||
| assert.ok(hiddenNode != null); | ||
| assert.equal(visibleNode.hidden, false); | ||
| assert.equal(visibleNode.username, "visibletree"); | ||
| assert.equal(visibleNode.name, "Visible Tree"); | ||
|
|
||
| assert.equal(hiddenNode.hidden, true); | ||
| assert.equal(hiddenNode.username, null); | ||
| assert.equal(hiddenNode.name, null); | ||
| assert.equal( | ||
| hiddenNode.avatarUrl, | ||
| "https://gravatar.com/avatar/?d=mp&s=128", | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| test("updateAccount updates profile preferences for the signed-in account", async () => { | ||
| await withRollback(async (tx) => { | ||
| const account = await insertAccountWithActor(tx, { | ||
| username: "updateaccountgraphql", | ||
| name: "Update Account GraphQL", | ||
| email: "updateaccountgraphql@example.com", | ||
| }); | ||
|
|
||
| const fedCtx = createFedCtx(tx); | ||
| fedCtx.getActor = (identifier: string) => | ||
| Promise.resolve( | ||
| new vocab.Person({ | ||
| id: fedCtx.getActorUri(identifier), | ||
| }), | ||
| ); | ||
|
|
||
| const result = await execute({ | ||
| schema, | ||
| document: updateAccountMutation, | ||
| variableValues: { | ||
| input: { | ||
| id: encodeGlobalID("Account", account.account.id), | ||
| bio: "Updated profile bio", | ||
| locales: ["ko-KR", "en-US"], | ||
| preferAiSummary: true, | ||
| hideFromInvitationTree: true, | ||
| hideForeignLanguages: true, | ||
| defaultNoteVisibility: "FOLLOWERS", | ||
| defaultShareVisibility: "UNLISTED", | ||
| }, | ||
| }, | ||
| contextValue: makeUserContext(tx, account.account, { fedCtx }), | ||
| onError: "NO_PROPAGATE", | ||
| }); | ||
|
|
||
| assert.equal(result.errors, undefined); | ||
| assert.deepEqual(toPlainJson(result.data), { | ||
| updateAccount: { | ||
| account: { | ||
| username: "updateaccountgraphql", | ||
| bio: "Updated profile bio", | ||
| locales: ["ko-KR", "en-US"], | ||
| preferAiSummary: true, | ||
| defaultNoteVisibility: "FOLLOWERS", | ||
| defaultShareVisibility: "UNLISTED", | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| const stored = await tx.query.accountTable.findFirst({ | ||
| where: { id: account.account.id }, | ||
| }); | ||
| assert.ok(stored != null); | ||
| assert.equal(stored.hideFromInvitationTree, true); | ||
| assert.equal(stored.hideForeignLanguages, true); | ||
| assert.deepEqual(stored.locales, ["ko-KR", "en-US"]); | ||
| assert.equal(stored.preferAiSummary, true); | ||
| assert.equal(stored.noteVisibility, "followers"); | ||
| assert.equal(stored.shareVisibility, "unlisted"); | ||
| }); | ||
| }); | ||
|
|
||
| test("updateAccount rejects a second username change", async () => { | ||
| await withRollback(async (tx) => { | ||
| const account = await insertAccountWithActor(tx, { | ||
| username: "renameonce", | ||
| name: "Rename Once", | ||
| email: "renameonce@example.com", | ||
| }); | ||
|
|
||
| const renamed = await updateAccountData(tx, { | ||
| id: account.account.id, | ||
| username: "renamedonce", | ||
| }); | ||
| assert.ok(renamed != null); | ||
| assert.ok(renamed.usernameChanged != null); | ||
|
|
||
| const result = await execute({ | ||
| schema, | ||
| document: updateAccountMutation, | ||
| variableValues: { | ||
| input: { | ||
| id: encodeGlobalID("Account", account.account.id), | ||
| username: "renamedtwice", | ||
| }, | ||
| }, | ||
| contextValue: makeUserContext(tx, { ...account.account, ...renamed }), | ||
| onError: "NO_PROPAGATE", | ||
| }); | ||
|
|
||
| assert.deepEqual(toPlainJson(result.data), { updateAccount: null }); | ||
| assert.equal(result.errors?.length, 1); | ||
| assert.equal( | ||
| result.errors?.[0].message, | ||
| "Username cannot be changed after it has been changed.", | ||
| ); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.