nodejs: add tests to library (#7694)

* nodejs: add tests to library

* new tests

* remove message splitting

* fix race

* docs

* replace extract-zip with yauzl that it wraps (high severity issue)

* fix ci tests

* skip server tests on macos and win unless SMP_SERVER is set

* fix win test

* fix win test

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
EvgenyandEvgeny @ SimpleX Chat authored and GitHub committed 2026-10-19 19:49:44 +01:00
1 parent ba9269a3ad
commit 46b2cf9f85
73 files changed
+957 -265

No files matched your search

+38 -28
View File
@@ -2,17 +2,25 @@ import * as path from "path"
import * as fs from "fs"
import {CEvt, T} from "@simplex-chat/types"
import {api} from ".."
import {SmpServer, describeIfSmpServerAvailable, startSmpServer} from "./smpServer"
const CT = T.ChatType
describe("API tests (use preset servers)", () => {
describeIfSmpServerAvailable("API tests (use local SMP server)", () => {
const tmpDir = "./tests/tmp"
const alicePath = path.join(tmpDir, "alice")
const bobPath = path.join(tmpDir, "bob")
let smpServer: SmpServer
beforeAll(async () => { smpServer = await startSmpServer() }, 120000)
afterAll(() => smpServer?.stop())
beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true}))
afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true}))
async function useSmpServer(chat: api.ChatApi): Promise<void> {
expect(await chat.sendChatCmd(`/smp ${smpServer.address}`)).toMatchObject({type: "cmdOk"})
}
it("should send/receive message", async () => {
// create users and start chat controllers
const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath})
@@ -27,32 +35,37 @@ describe("API tests (use preset servers)", () => {
await bob.apiCreateActiveUser({displayName: "bob", fullName: ""})
await alice.startChat()
await bob.startChat()
await useSmpServer(alice)
await useSmpServer(bob)
// connect via link
const link = await alice.apiCreateLink(aliceUser.userId)
const connected = Promise.all([alice.wait("contactConnected"), bob.wait("contactConnected")])
await expect(bob.apiConnectActiveUser(link)).resolves.toBe(api.ConnReqType.Invitation)
const [bobContact, aliceContact] = await Promise.all([
(await alice.wait("contactConnected")).contact,
(await bob.wait("contactConnected")).contact
])
const [{contact: bobContact}, {contact: aliceContact}] = await connected
expect(bobContact).toMatchObject({profile: {displayName: "bob"}})
expect(aliceContact).toMatchObject({profile: {displayName: "alice"}})
// exchange messages
const isMessage = ({contactId}: T.Contact, msg: string) => (evt: CEvt.NewChatItems) =>
evt.chatItems.some(ci => ci.chatInfo.type === CT.Direct && ci.chatInfo.contact.contactId === contactId && ci.chatItem.meta.itemText === msg)
const hello = bob.wait("newChatItems", isMessage(aliceContact, "hello"))
await alice.apiSendTextMessage([CT.Direct, bobContact.contactId], "hello")
await bob.wait("newChatItems", isMessage(aliceContact, "hello"))
await hello
const helloToo = alice.wait("newChatItems", isMessage(bobContact, "hello too"), 10000)
await bob.apiSendTextMessage([CT.Direct, aliceContact.contactId], "hello too")
await alice.wait("newChatItems", isMessage(bobContact, "hello too"), 10000)
await expect(helloToo).resolves.toBeDefined()
const howAreYou = bob.wait("newChatItems", isMessage(aliceContact, "how are you?"))
await alice.apiSendTextMessage([CT.Direct, bobContact.contactId], "how are you?")
await bob.wait("newChatItems", isMessage(aliceContact, "how are you?"))
await howAreYou
const okAndYou = alice.wait("newChatItems", isMessage(bobContact, "ok, and you?"), 10000)
await bob.apiSendTextMessage([CT.Direct, aliceContact.contactId], "ok, and you?")
await alice.wait("newChatItems", isMessage(bobContact, "ok, and you?"), 10000)
await expect(okAndYou).resolves.toBeDefined()
// no more messages
await expect(alice.wait("newChatItems", 500)).resolves.toBeUndefined()
await expect(bob.wait("newChatItems", 500)).resolves.toBeUndefined()
// delete contacts, stop chat controllers and close databases
const deleted = bob.wait("contactDeletedByContact")
await alice.apiDeleteChat(CT.Direct, bobContact.contactId)
await bob.wait("contactDeletedByContact")
await deleted
await bob.apiDeleteChat(CT.Direct, aliceContact.contactId)
await alice.stopChat()
await bob.stopChat()
@@ -60,8 +73,7 @@ describe("API tests (use preset servers)", () => {
await bob.close()
await expect(alice.startChat()).rejects.toThrow("chat api controller not initialized")
await expect(bob.startChat()).rejects.toThrow("chat api controller not initialized")
expect(servers.length).toBe(2)
expect(servers[0] !== servers[1]).toBe(true)
expect([...new Set(servers)]).toEqual(["localhost"])
expect(eventCount > 0).toBe(true)
}, 30000)
@@ -77,20 +89,19 @@ describe("API tests (use preset servers)", () => {
await alice.startChat()
await bob.startChat()
await carol.startChat()
await useSmpServer(alice)
await useSmpServer(bob)
await useSmpServer(carol)
// connect alice <-> bob
const aliceLink1 = await alice.apiCreateLink(aliceUser.userId)
const bobConnected = Promise.all([alice.wait("contactConnected"), bob.wait("contactConnected")])
await expect(bob.apiConnectActiveUser(aliceLink1)).resolves.toBe(api.ConnReqType.Invitation)
const [bobContact] = await Promise.all([
(await alice.wait("contactConnected")).contact,
(await bob.wait("contactConnected")).contact
])
const [{contact: bobContact}] = await bobConnected
// connect alice <-> carol
const aliceLink2 = await alice.apiCreateLink(aliceUser.userId)
const carolConnected = Promise.all([alice.wait("contactConnected"), carol.wait("contactConnected")])
await expect(carol.apiConnectActiveUser(aliceLink2)).resolves.toBe(api.ConnReqType.Invitation)
const [carolContact] = await Promise.all([
(await alice.wait("contactConnected")).contact,
(await carol.wait("contactConnected")).contact
])
const [{contact: carolContact}] = await carolConnected
// create group with direct messages enabled
const group = await alice.apiNewGroup(aliceUser.userId, {
displayName: "test-group",
@@ -105,10 +116,10 @@ describe("API tests (use preset servers)", () => {
await alice.apiAddMember(groupId, bobContact.contactId, T.GroupMemberRole.Member)
const bobInvEvt = await bobInvP
expect(bobInvEvt).toBeDefined()
const aliceBobConnP = alice.wait("connectedToGroupMember", 15000)
const bobAliceConnP = bob.wait("connectedToGroupMember", 15000)
const aliceBobConnP = alice.wait("joinedGroupMember", 15000)
const bobAliceConnP = bob.wait("userJoinedGroup", 15000)
await bob.apiJoinGroup(bobInvEvt!.groupInfo.groupId)
await Promise.all([aliceBobConnP, bobAliceConnP])
expect(await Promise.all([aliceBobConnP, bobAliceConnP])).not.toContain(undefined)
// add carol to the group
const carolInvP = carol.wait("receivedGroupInvitation", 30000)
await alice.apiAddMember(groupId, carolContact.contactId, T.GroupMemberRole.Member)
@@ -117,14 +128,13 @@ describe("API tests (use preset servers)", () => {
// wait for carol to connect to both alice and bob (and vice versa)
const bobCarolConnP = bob.wait("connectedToGroupMember",
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "carol", 30000)
const carolAliceConnP = carol.wait("connectedToGroupMember",
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "alice", 30000)
const carolAliceConnP = carol.wait("userJoinedGroup", 30000)
const carolBobConnP = carol.wait("connectedToGroupMember",
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "bob", 30000)
const aliceCarolConnP = alice.wait("connectedToGroupMember",
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "carol", 30000)
const aliceCarolConnP = alice.wait("joinedGroupMember",
(evt: CEvt.JoinedGroupMember) => evt.member.memberProfile.displayName === "carol", 30000)
await carol.apiJoinGroup(carolInvEvt!.groupInfo.groupId)
await Promise.all([bobCarolConnP, carolAliceConnP, carolBobConnP, aliceCarolConnP])
expect(await Promise.all([bobCarolConnP, carolAliceConnP, carolBobConnP, aliceCarolConnP])).not.toContain(undefined)
// find carol's memberId from bob's perspective
const members = await bob.apiListMembers(groupId)
const carolMember = members.find(m => m.memberProfile.displayName === "carol")