Commit bb5d7b521b6c8e85191dc1198328e14fa17a170d

Parents: ed92ed6c8a628c10eb19a15d5bf4a6de19309b46

From: Moritz Poldrack <git@moritz.sh>
Date: Sat Jun 11 01:46:46 2022 +0700

added BODY command

		

Stats

README.md +1/-1
reader.go +26/-0
reader_test.go +62/-0
status.go +1/-0

Changeset

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
diff --git a/README.md b/README.md
index 4751ddd2bdbb922c253ac5fc0f52ced1ae62c57f..43e8950c3ee7bebc4ba0f0f3a099492892d6086e 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ | OVER              | OVER          | ☓           | RFC3977  |
 | LIST OVERVIEW.FMT | OVER          | ☓           | RFC3977  |
 | POST              | POST          | ☓           | RFC3977  |
 | ARTICLE           | READER        | 🗸           | RFC3977  |
-| BODY              | READER        | ☓           | RFC3977  |
+| BODY              | READER        | 🗸           | RFC3977  |
 | DATE              | READER        | ☓           | RFC3977  |
 | GROUP             | READER        | 🗸           | RFC3977  |
 | LAST              | READER        | ☓           | RFC3977  |
diff --git a/reader.go b/reader.go
index d68f64711105cbb07344e97ca03590b73389b7ca..0285c7ee2ca93d2bae93ab1c59be5d13bff8b6ff 100644
--- a/reader.go
+++ b/reader.go
@@ -31,6 +31,32 @@ 		return resp, ErrUnexpectedResponse
 	}
 }
 
+func (c *Conn) Body(ctx context.Context, msg MessageIdentifier) (*Response, error) {
+	if c.caps&CapReader == 0 {
+		return nil, ErrCapabilityNotSupported
+	}
+
+	resp, err := c.Cmd(ctx, "BODY %s", msg.msgID())
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve article '%s': %w", msg.msgID(), err)
+	}
+
+	switch resp.Status.Code {
+	case StatusBodyFollows:
+		return resp, nil
+	case StatusNoSuchNewsgroup:
+		return resp, ErrNoNewsgroupSelected
+	case StatusCurrentArticleNumberInvalid:
+		return resp, ErrCurrentArticleNumberInvalid
+	case StatusNoArticleWithGivenMessageID:
+		return resp, ErrNoArticleWithGivenMessageID
+	case StatusNoArticleWithGivenNumber:
+		return resp, ErrNoArticleWithGivenNumber
+	default:
+		return resp, ErrUnexpectedResponse
+	}
+}
+
 func (c *Conn) Group(ctx context.Context, group string) error {
 	if c.caps&CapReader == 0 {
 		return ErrCapabilityNotSupported
diff --git a/reader_test.go b/reader_test.go
index 8394e287e0add6f8318f272aa054e67df0465479..4d20970b2185d548f38b8184e02891d90f67be1b 100644
--- a/reader_test.go
+++ b/reader_test.go
@@ -86,6 +86,68 @@ 		})
 	}
 }
 
+func TestBody(t *testing.T) {
+	if NewsServerSecure == "" || NewsServerUser == "" || NewsServerPassword == "" {
+		t.Log("secure server address, username, and password required in variables_test.go")
+		t.SkipNow()
+	}
+	tests := []struct {
+		name          string
+		message       nntp.MessageIdentifier
+		expectedError error
+		validate      func(*testing.T, *nntp.Response)
+	}{
+		/*{ // Eweka sends 411 instead of 412
+			name:          "no-selected-group",
+			message:       nntp.ArticleNumber(54160654),
+			expectedError: nntp.ErrNoSuchNewsgroup,
+			validate:      func(t *testing.T, resp *nntp.Response) { fmt.Println(resp.Status.String()); t.Fail() },
+		},*/
+		{
+			name:          "non-existant-article",
+			message:       nntp.MessageID("probably-not-valid-@some-client.lol"),
+			expectedError: nntp.ErrNoArticleWithGivenMessageID,
+			validate:      func(t *testing.T, r *nntp.Response) {},
+		},
+		{
+			name:          "existing-article",
+			message:       nntp.MessageID("HtIsMuKsOrQwViIoPiBzAsZy-1654899257340@nyuu"),
+			expectedError: nil,
+			validate: func(t *testing.T, r *nntp.Response) {
+				if len(r.Body) != 98 {
+					t.Errorf("length of body '%d' does not match expectation 98", len(r.Body))
+				}
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			c, err := nntp.Dial(NewsServerSecure)
+			if err != nil {
+				t.Skipf("connection to '%s' failed: %v", NewsServerSecure, err)
+			}
+			defer c.Close(context.Background())
+
+			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+			defer cancel()
+			err = c.LoginUserPass(ctx, NewsServerUser, NewsServerPassword)
+			if err != nil {
+				t.Skipf("login failed: %v", err)
+			}
+
+			ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
+			defer cancel()
+			resp, err := c.Body(ctx, test.message)
+			if !errors.Is(err, test.expectedError) {
+				t.Errorf("unexpected result:\n\tgot:      %v\n\texpected: %v\n", err, test.expectedError)
+			}
+
+			test.validate(t, resp)
+		})
+	}
+}
+
 func TestGroup(t *testing.T) {
 	if NewsServerSecure == "" || NewsServerUser == "" || NewsServerPassword == "" {
 		t.Log("secure server address, username, and password required in variables_test.go")
diff --git a/status.go b/status.go
index d604742694b2e22e17368fb686b1bf5ba32b3420..b884707119a292035598616fa66db11ee917c7dc 100644
--- a/status.go
+++ b/status.go
@@ -9,6 +9,7 @@ 	StatusServiceNoPosting            = 201
 	StatusConnectionClosing           = 205
 	StatusGroupSelected               = 211
 	StatusArticleFollows              = 220
+	StatusBodyFollows                 = 222
 	StatusHeadersFollow               = 221
 	StatusArticleExists               = 223
 	StatusAuthAccepted                = 281