newsie
Version:
An NNTP Client Library targeting NodeJS. It supports the authentication, TLS encryption, base NNTP commands, and more.
516 lines (440 loc) • 15.1 kB
text/typescript
import {
Article,
ArticleResponse,
GroupResponse,
GroupsResponse,
NewnewsResponse,
NntpErrorResponse,
NntpResponse,
PostResponse
} from '../src'
import { c, client, integrationSetup, s } from './IntegrationCommon'
integrationSetup()
// Note: RFC977 is deprecated in favor of RFC3977, test cases note
// where the two RFCs conflict.
describe(`4. Sample Conversations
These are samples of the conversations that might be expected with
the news server in hypothetical sessions. The notation C: indicates
commands sent to the news server from the client program; S: indicate
responses received from the server by the client.`, () => {
test('4.1. Example 1 - relative access with NEXT', async () => {
// s('(listens at TCP port 119)')
// c('(requests connection on TCP port 119)')
// s('200 wombatvax news server ready - posting ok')
// (client asks for a current newsgroup list)
c('LIST')
s('215 list of newsgroups follows')
s('net.wombats 00543 00501 y')
s('net.unix-wizards 10125 10011 y')
// (more information here)
s('net.idiots 00100 00001 n')
s('.')
let response = await client.list()
expect(response).toEqual({
code: 215,
comment: 'list of newsgroups follows',
description: 'Information follows (multi-line)',
newsgroups: [
{
name: 'net.wombats',
high: 543,
low: 501,
status: 'y'
},
{
name: 'net.unix-wizards',
high: 10125,
low: 10011,
status: 'y'
},
{
name: 'net.idiots',
high: 100,
low: 1,
status: 'n'
}
]
} as GroupsResponse)
// (client selects a newsgroup)
c('GROUP net.unix-wizards')
s('211 104 10011 10125 net.unix-wizards group selected')
// (there are 104 articles on file, from 10011 to 10125)
response = await client.group('net.unix-wizards')
expect(response).toEqual({
code: 211,
comment: 'group selected',
description: 'Group successfully selected',
group: {
number: 104,
low: 10011,
high: 10125,
name: 'net.unix-wizards'
}
} as GroupResponse)
// (client selects an article to read)
c('STAT 10110')
s('223 10110 <23445@sdcsvax.ARPA> article retrieved - statistics')
// only (article 10110 selected, its message-id is <23445@sdcsvax.ARPA>)
response = await client.stat(10110)
expect(response).toEqual({
code: 223,
comment: 'article retrieved - statistics',
description: 'Article exists',
article: {
articleNumber: 10110,
messageId: '<23445@sdcsvax.ARPA>'
}
} as ArticleResponse)
// (client examines the header)
c('HEAD')
s('221 10110 <23445@sdcsvax.ARPA> article retrieved - head')
// follows (text of the header appears here)
s('.')
response = await client.head()
expect(response).toEqual({
code: 221,
comment: 'article retrieved - head',
description: 'Headers follow (multi-line)',
article: {
articleNumber: 10110,
messageId: '<23445@sdcsvax.ARPA>',
headers: {}
}
} as ArticleResponse)
// (client wants to see the text body of the article)
c('BODY')
s('222 10110 <23445@sdcsvax.ARPA> article retrieved - body')
// follows (body text here)
s('.')
response = await client.body()
expect(response).toEqual({
code: 222,
comment: 'article retrieved - body',
description: 'Body follows (multi-line)',
article: {
articleNumber: 10110,
messageId: '<23445@sdcsvax.ARPA>',
body: []
}
} as ArticleResponse)
// (client selects next article in group)
c('NEXT')
s('223 10113 <21495@nudebch.uucp> article retrieved - statistics')
// only (article 10113 was next in group)
response = await client.next()
expect(response).toEqual({
code: 223,
comment: 'article retrieved - statistics',
description: 'Article found',
article: {
articleNumber: 10113,
messageId: '<21495@nudebch.uucp>'
}
} as ArticleResponse)
// (client finishes session)
c('QUIT')
s('205 goodbye.')
response = await client.quit()
expect(response).toEqual({
code: 205,
comment: 'NNTP Service exits normally',
description: 'Connection closing'
} as NntpResponse)
})
test('4.2. Example 2 - absolute article access with ARTICLE', async () => {
// s('(listens at TCP port 119)')
// c('(requests connection on TCP port 119)')
// s('201 UCB-VAX netnews server ready -- no posting allowed')
c('GROUP msgs')
s('211 103 402 504 msgs Your new group is msgs')
// (there are 103 articles, from 402 to 504)
let response = await client.group('msgs')
expect(response).toEqual({
code: 211,
comment: 'Your new group is msgs',
description: 'Group successfully selected',
group: {
number: 103,
low: 402,
high: 504,
name: 'msgs'
}
} as GroupResponse)
c('ARTICLE 401')
s('423 No such article in this newsgroup')
let caught = false
try {
await client.article(401)
} catch (response) {
expect(response).toEqual({
code: 423,
comment: 'No such article in this newsgroup',
description: 'No article with that number'
} as NntpErrorResponse)
caught = true
}
expect(caught).toBe(true)
c('ARTICLE 402')
s('220 402 <4105@ucbvax.ARPA> Article retrieved, text follows')
// article header and body follow
s('')
s('.')
response = await client.article(402)
expect(response).toEqual({
code: 220,
comment: 'Article retrieved, text follows',
description: 'Article follows (multi-line)',
article: {
articleNumber: 402,
messageId: '<4105@ucbvax.ARPA>',
headers: {},
body: []
}
} as ArticleResponse)
c('HEAD 403')
s('221 403 <3108@mcvax.UUCP> Article retrieved, header follows')
// article header follows
s('.')
response = await client.head(403)
expect(response).toEqual({
code: 221,
comment: 'Article retrieved, header follows',
description: 'Headers follow (multi-line)',
article: {
articleNumber: 403,
messageId: '<3108@mcvax.UUCP>',
headers: {}
}
} as ArticleResponse)
c('QUIT')
s('205 UCB-VAX news server closing connection. Goodbye.')
response = await client.quit()
expect(response).toEqual({
code: 205,
comment: 'NNTP Service exits normally',
description: 'Connection closing'
} as NntpErrorResponse)
})
test('4.3. Example 3 - NEWGROUPS command', async () => {
// s('(listens at TCP port 119)')
// c('(requests connection on TCP port 119)')
// s('200 Imaginary Institute News Server ready (posting ok)')
// (client asks for new newsgroups since April 3, 1985)
c('NEWGROUPS 850403 020000')
s('231 New newsgroups since 03/04/85 02:00:00 follow')
s('net.music.gdead')
s('net.games.sources')
s('.')
// Note: RFC977 NEWGROUPS command conflicts with RFC3977 NEWGROUPS command:
// In RFC977, the command returns 1 groups per line
// In RFC3977 the command returns 4 fields per line
// let response = await client.newgroups('1985-04-03T02:00:00Z')
// let response = await client.command('NEWGROUPS', '850403', '020000')
// expect(response).toEqual({
// code: 231,
// comment: 'New newsgroups since 03/04/85 02:00:00 follow',
// description: 'List of new newsgroups follows (multi-line)',
// newsgroups: [
// {
// name: 'net.music.gdead',
// },
// {
// name: 'net.games.sources',
// }
// ]
// })
c('GROUP net.music.gdead')
s('211 0 1 1 net.music.gdead Newsgroup selected')
// (there are no articles in that newsgroup, and
// the first and last article numbers should be ignored)
let response = await client.group('net.music.gdead')
expect(response).toEqual({
code: 211,
comment: 'Newsgroup selected',
description: 'Group successfully selected',
group: {
number: 0,
low: 1,
high: 1,
name: 'net.music.gdead'
}
} as GroupResponse)
c('QUIT')
s('205 Imaginary Institute news server ceasing service. Bye!')
response = await client.quit()
expect(response).toEqual({
code: 205,
comment: 'NNTP Service exits normally',
description: 'Connection closing'
} as NntpResponse)
})
test('4.4. Example 4 - posting a news article', async () => {
// s('(listens at TCP port 119)')
// c('(requests connection on TCP port 119)')
// s('200 BANZAIVAX news server ready, posting allowed.')
c('POST')
s('340 Continue posting; Period on a line by itself to end')
let response = await client.post()
expect(response).toEqual({
code: 340,
comment: 'Continue posting; Period on a line by itself to end',
description: 'Send article to be posted',
send: expect.any(Function)
} as PostResponse)
// Note: RFC977's above comment invalidated by RFC3977's ABNF which shows
// an empty article still has a CR-LF for headers
// (transmits news article in RFC850 format)
c('', '.')
s('240 Article posted successfully.')
response = await response.send({
headers: {},
body: []
})
expect(response).toEqual({
code: 240,
comment: 'Article posted successfully.',
description: 'Article received OK'
} as NntpResponse)
c('QUIT')
s('205 BANZAIVAX closing connection. Goodbye.')
response = await client.quit()
expect(response).toEqual({
code: 205,
comment: 'NNTP Service exits normally',
description: 'Connection closing'
} as NntpResponse)
})
test('4.5. Example 5 - interruption due to operator request', async () => {
s('(listens at TCP port 119)')
c('(requests connection on TCP port 119)')
s('201 genericvax news server ready, no posting allowed.')
// (assume normal conversation for some time, and
// that a newsgroup has been selected)
c('NEXT')
s('223 1013 <5734@mcvax.UUCP> Article retrieved; text separate.')
const response = await client.next()
expect(response).toEqual({
code: 223,
comment: 'Article retrieved; text separate.',
description: 'Article found',
article: {
articleNumber: 1013,
messageId: '<5734@mcvax.UUCP>'
}
} as ArticleResponse)
c('HEAD')
c('221 1013 <5734@mcvax.UUCP> Article retrieved; head follows.')
// s(`(sends head of article, but halfway through is
// interrupted by an operator request. The following
// then occurs, without client intervention.)`)
// s('(ends current line with a CR-LF pair)')
s('.')
s('400 Connection closed by operator. Goodbye.')
// s('(closes connection)')
// Note: in RFC3977 the client does not take random error codes from server,
// but expects the 400 as a response to the next command
})
test(`4.6. Example 6 - Using the news server to distribute news between
systems.`, async () => {
// s('(listens at TCP port 119)')
// c('(requests connection on TCP port 119)')
// s('201 Foobar NNTP server ready (no posting)')
// (client asks for new newsgroups since 2 am, May 15, 1985)
c('NEWGROUPS 850515 020000')
s('235 New newsgroups since 850515 follow')
s('net.fluff')
s('net.lint')
s('.')
// let response = await client.newgroups('1985-05-15T02:00:00Z')
// let response = await client.command('NEWGROUPS', '850515', '020000')
// NOTE: RFC977 and RFC3977 have different responses to NEWGROUPS command
// (client asks for new news articles since 2 am, May 15, 1985)
c('NEWNEWS * 850515 020000')
s('230 New news since 850515 020000 follows')
s('<1772@foo.UUCP>')
s('<87623@baz.UUCP>')
s('<17872@GOLD.CSNET>')
s('.')
// let response = await client.newnews('*', '1985-05-15T02:00:00')
let response = await client.command('NEWNEWS', '*', '850515', '020000')
expect(response).toEqual({
code: 230,
comment: 'New news since 850515 020000 follows',
description: 'List of new articles follows (multi-line)',
messageIds: ['<1772@foo.UUCP>', '<87623@baz.UUCP>', '<17872@GOLD.CSNET>']
} as NewnewsResponse)
// (client asks for article <1772@foo.UUCP>)
c('ARTICLE <1772@foo.UUCP>')
s('220 <1772@foo.UUCP> All of article follows')
// s('(sends entire message)')
s('')
s('.')
// response = await client.article('<1772@foo.UUCP>')
// expect(response).toEqual({
// code: 220,
// comment: '',
// description: 'Article follows (multi-line)',
// article: {
// articleNumber: /* ??? */0,
// messageId: '<1772@foo.UUCP>',
// headers: {},
// body: []
// }
// })
// Note: RFC977 is missing articleNumber from RFC3977 in response
// (client asks for article <87623@baz.UUCP>
c('ARTICLE <87623@baz.UUCP>')
s('220 <87623@baz.UUCP> All of article follows')
s('(sends entire message)')
s('.')
// Note: RFCs conflict, see above
// (client asks for article <17872@GOLD.CSNET>
c('ARTICLE <17872@GOLD.CSNET>')
s('220 <17872@GOLD.CSNET> All of article follows')
s('(sends entire message)')
s('.')
// Note: RFCs conflict, see above
// (client offers an article it has received recently)
c('IHAVE <4105@ucbvax.ARPA>')
s('435 Already seen that one, where you been?')
let caught = false
try {
await client.ihave('<4105@ucbvax.ARPA>')
} catch (response) {
caught = true
expect(response).toEqual({
code: 435,
comment: 'Already seen that one, where you been?',
description: 'Article not wanted'
} as NntpErrorResponse)
}
expect(caught).toBe(true)
// (client offers another article)
c('IHAVE <4106@ucbvax.ARPA>')
s('335 News to me! <CRLF.CRLF> to end.')
response = await client.ihave('<4106@ucbvax.ARPA>')
expect(response).toEqual({
code: 335,
comment: 'News to me! <CRLF.CRLF> to end.',
description: 'Send article to be transferred',
send: expect.any(Function)
} as PostResponse)
// c('(sends article)')
c('.')
// Note: In RFC3977, a single dot is a syntax error
// s('235 Article transferred successfully. Thanks.')
//
// (or)
//
// s('436 Transfer failed.')
// (client is all through with the session)
c('QUIT')
s('205 Foobar NNTP server bids you farewell.')
response = await client.quit()
expect(response).toEqual({
code: 205,
comment: 'NNTP Service exits normally',
description: 'Connection closing'
} as NntpResponse)
})
})