mirror of
https://gitlab.com/ric_harvey/MailHog.git
synced 2024-11-27 16:24:04 +00:00
Add HTTP server and assets
This commit is contained in:
parent
c4aebd5d66
commit
4f4f921013
11 changed files with 366 additions and 25 deletions
|
@ -2,12 +2,20 @@ package mailhog
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
BindAddr: "0.0.0.0:1025",
|
SMTPBindAddr: "0.0.0.0:1025",
|
||||||
|
HTTPBindAddr: "0.0.0.0:8025",
|
||||||
Hostname: "mailhog.example",
|
Hostname: "mailhog.example",
|
||||||
|
MongoUri: "127.0.0.1:27017",
|
||||||
|
MongoDb: "mailhog",
|
||||||
|
MongoColl: "messages",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
BindAddr string
|
SMTPBindAddr string
|
||||||
|
HTTPBindAddr string
|
||||||
Hostname string
|
Hostname string
|
||||||
|
MongoUri string
|
||||||
|
MongoDb string
|
||||||
|
MongoColl string
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ func ContentFromString(data string) *Content {
|
||||||
if(strings.Contains(hdr, ": ")) {
|
if(strings.Contains(hdr, ": ")) {
|
||||||
y := strings.SplitN(hdr, ": ", 2)
|
y := strings.SplitN(hdr, ": ", 2)
|
||||||
key, value := y[0], y[1]
|
key, value := y[0], y[1]
|
||||||
|
// TODO multiple header fields
|
||||||
h[key] = []string{value}
|
h[key] = []string{value}
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Found invalid header: '%s'", hdr)
|
log.Printf("Found invalid header: '%s'", hdr)
|
||||||
|
|
50
mailhog/http/server.go
Normal file
50
mailhog/http/server.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"github.com/ian-kent/MailHog/mailhog"
|
||||||
|
"github.com/ian-kent/MailHog/mailhog/templates"
|
||||||
|
"github.com/ian-kent/MailHog/mailhog/templates/images"
|
||||||
|
"github.com/ian-kent/MailHog/mailhog/templates/js"
|
||||||
|
)
|
||||||
|
|
||||||
|
var exitChannel chan int
|
||||||
|
|
||||||
|
func web_exit(w http.ResponseWriter, r *http.Request) {
|
||||||
|
web_headers(w)
|
||||||
|
fmt.Fprint(w, "Exiting MailHog!")
|
||||||
|
exitChannel <- 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func web_index(w http.ResponseWriter, r *http.Request) {
|
||||||
|
web_headers(w)
|
||||||
|
fmt.Fprint(w, web_render(templates.Index()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func web_jscontroller(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/javascript")
|
||||||
|
fmt.Fprint(w, js.Controllers())
|
||||||
|
}
|
||||||
|
|
||||||
|
func web_imgcontroller(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "image/png")
|
||||||
|
w.Write(images.Hog())
|
||||||
|
}
|
||||||
|
|
||||||
|
func web_render(content string) string {
|
||||||
|
return templates.Layout(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func web_headers(w http.ResponseWriter) {
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(exitCh chan int, conf *mailhog.Config) {
|
||||||
|
exitChannel = exitCh
|
||||||
|
http.HandleFunc("/exit", web_exit)
|
||||||
|
http.HandleFunc("/js/controllers.js", web_jscontroller)
|
||||||
|
http.HandleFunc("/images/hog.png", web_imgcontroller)
|
||||||
|
http.HandleFunc("/", web_index)
|
||||||
|
http.ListenAndServe(conf.HTTPBindAddr, nil)
|
||||||
|
}
|
|
@ -28,7 +28,6 @@ const (
|
||||||
DONE
|
DONE
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO add Received/Return-Path headers
|
|
||||||
// TODO replace ".." lines with . in data
|
// TODO replace ".." lines with . in data
|
||||||
|
|
||||||
func StartSession(conn *net.TCPConn, conf *mailhog.Config) {
|
func StartSession(conn *net.TCPConn, conf *mailhog.Config) {
|
||||||
|
@ -39,7 +38,7 @@ func StartSession(conn *net.TCPConn, conf *mailhog.Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Session) log(message string, args ...interface{}) {
|
func (c *Session) log(message string, args ...interface{}) {
|
||||||
message = strings.Join([]string{"[%s, %d]", message}, " ")
|
message = strings.Join([]string{"[SMTP %s, %d]", message}, " ")
|
||||||
args = append([]interface{}{c.conn.RemoteAddr(), c.state}, args...)
|
args = append([]interface{}{c.conn.RemoteAddr(), c.state}, args...)
|
||||||
log.Printf(message, args...)
|
log.Printf(message, args...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,13 @@ import (
|
||||||
|
|
||||||
func Store(c *mailhog.Config, m *data.SMTPMessage) (string, error) {
|
func Store(c *mailhog.Config, m *data.SMTPMessage) (string, error) {
|
||||||
msg := data.ParseSMTPMessage(c, m)
|
msg := data.ParseSMTPMessage(c, m)
|
||||||
session, err := mgo.Dial("localhost:27017")
|
session, err := mgo.Dial(c.MongoUri)
|
||||||
if(err != nil) {
|
if(err != nil) {
|
||||||
log.Printf("Error connecting to MongoDB: %s", err)
|
log.Printf("Error connecting to MongoDB: %s", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
err = session.DB("mailhog").C("messages").Insert(msg)
|
err = session.DB(c.MongoDb).C(c.MongoColl).Insert(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error inserting message: %s", err)
|
log.Printf("Error inserting message: %s", err)
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -24,15 +24,15 @@ func Store(c *mailhog.Config, m *data.SMTPMessage) (string, error) {
|
||||||
return msg.Id, nil
|
return msg.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load(id string) (*data.Message, error) {
|
func Load(c *mailhog.Config, id string) (*data.Message, error) {
|
||||||
session, err := mgo.Dial("localhost:27017")
|
session, err := mgo.Dial(c.MongoUri)
|
||||||
if(err != nil) {
|
if(err != nil) {
|
||||||
log.Printf("Error connecting to MongoDB: %s", err)
|
log.Printf("Error connecting to MongoDB: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
result := &data.Message{}
|
result := &data.Message{}
|
||||||
err = session.DB("mailhog").C("messages").Find(bson.M{"id": id}).One(&result)
|
err = session.DB(c.MongoDb).C(c.MongoColl).Find(bson.M{"id": id}).One(&result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error loading message: %s", err)
|
log.Printf("Error loading message: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
60
mailhog/templates/images/hog.go
Normal file
60
mailhog/templates/images/hog.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Hog() []byte {
|
||||||
|
hog := `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
|
||||||
|
qgAAAABJRU5ErkJggg==`
|
||||||
|
data, _ := base64.StdEncoding.DecodeString(hog)
|
||||||
|
return data
|
||||||
|
}
|
90
mailhog/templates/index.go
Normal file
90
mailhog/templates/index.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
func Index() string {
|
||||||
|
return `
|
||||||
|
<style>
|
||||||
|
.messages {
|
||||||
|
height: 30%;
|
||||||
|
}
|
||||||
|
.preview {
|
||||||
|
height: 70%;
|
||||||
|
border-top: 1px solid #CCCCCC;
|
||||||
|
}
|
||||||
|
.preview #headers {
|
||||||
|
border-bottom: 1px solid #DDDDDD;
|
||||||
|
}
|
||||||
|
.selected {
|
||||||
|
background: #DADAFA;
|
||||||
|
}
|
||||||
|
table tbody {
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
table td {
|
||||||
|
padding: 2px 4px 2px 4px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="modal fade" id="confirm-delete-all">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title">Delete all messages?</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to delete all messages?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-danger" ng-click="deleteAllConfirm()">Delete all messages</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages">
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>From</th>
|
||||||
|
<th>To</th>
|
||||||
|
<th>Subject</th>
|
||||||
|
<th>Received</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="message in messages" ng-click="selectMessage(message)" ng-class="{ selected: message == preview }">
|
||||||
|
<td>
|
||||||
|
{{ message.from.mailbox }}@{{ message.from.domain }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span ng-repeat="to in message.to">
|
||||||
|
{{ to.mailbox }}@{{ to.domain }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ message.content.headers.Subject }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ date(message.created) }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-xs btn-default" title="Delete" ng-click="deleteOne(message)"><span class="glyphicon glyphicon-remove"></span></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="preview">
|
||||||
|
<table class="table" id="headers">
|
||||||
|
<tr ng-repeat="(header, value) in preview.content.headers">
|
||||||
|
<td>
|
||||||
|
{{ header }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ value }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{{ preview.content.body }}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
43
mailhog/templates/js/controllers.go
Normal file
43
mailhog/templates/js/controllers.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package js
|
||||||
|
|
||||||
|
func Controllers() string {
|
||||||
|
return `
|
||||||
|
var mailhogApp = angular.module('mailhogApp', []);
|
||||||
|
|
||||||
|
mailhogApp.controller('MailCtrl', function ($scope, $http) {
|
||||||
|
$scope.refresh = function() {
|
||||||
|
$http.get('/api/v1/messages').success(function(data) {
|
||||||
|
$scope.messages = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$scope.refresh();
|
||||||
|
|
||||||
|
$scope.date = function(timestamp) {
|
||||||
|
return (new Date(timestamp)).toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.selectMessage = function(message) {
|
||||||
|
$scope.preview = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.deleteAll = function() {
|
||||||
|
$('#confirm-delete-all').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.deleteAllConfirm = function() {
|
||||||
|
$('#confirm-delete-all').modal('hide');
|
||||||
|
$http.post('/api/v1/messages/delete').success(function() {
|
||||||
|
$scope.refresh();
|
||||||
|
$scope.preview = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.deleteOne = function(message) {
|
||||||
|
$http.post('/api/v1/messages/' + message._id + '/delete').success(function() {
|
||||||
|
if($scope.preview._id == message._id) $scope.preview = null;
|
||||||
|
$scope.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
}
|
64
mailhog/templates/layout.go
Normal file
64
mailhog/templates/layout.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Layout(content string) string {
|
||||||
|
html := `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html ng-app="mailhogApp">
|
||||||
|
<head>
|
||||||
|
<title>MailHog</title>
|
||||||
|
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
|
||||||
|
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.15/angular.js"></script>
|
||||||
|
<script src="/js/controllers.js"></script>
|
||||||
|
<style>
|
||||||
|
body, html { height: 100%; overflow: none; }
|
||||||
|
.navbar {
|
||||||
|
margin-bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.messages {
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
.navbar-header img {
|
||||||
|
height: 35px;
|
||||||
|
margin: 8px 0 0 5px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.navbar-nav.navbar-right:last-child {
|
||||||
|
margin-right: 0; /* bootstrap fix?! */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body ng-controller="MailCtrl">
|
||||||
|
<nav class="navbar navbar-default navbar-static-top" role="navigation">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<img src="/images/hog.png">
|
||||||
|
<a class="navbar-brand" href="#">MailHog</a>
|
||||||
|
</div>
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Options <b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#" ng-click="refresh()">Refresh</a></li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li><a href="#" ng-click="deleteAll()">Delete all messages</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a target="_blank" href="https://github.com/ian-kent/MailHog">GitHub</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<%= content %>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
return strings.Replace(html, "<%= content %>", content, -1);
|
||||||
|
}
|
53
main.go
53
main.go
|
@ -4,36 +4,70 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"github.com/ian-kent/MailHog/mailhog"
|
"github.com/ian-kent/MailHog/mailhog"
|
||||||
|
"github.com/ian-kent/MailHog/mailhog/http"
|
||||||
"github.com/ian-kent/MailHog/mailhog/smtp"
|
"github.com/ian-kent/MailHog/mailhog/smtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var conf *mailhog.Config
|
var conf *mailhog.Config
|
||||||
|
var exitCh chan int
|
||||||
|
|
||||||
func config() {
|
func config() {
|
||||||
var listen, hostname string
|
var smtpbindaddr, httpbindaddr, hostname, mongouri, mongodb, mongocoll string
|
||||||
|
|
||||||
flag.StringVar(&listen, "listen", "0.0.0.0:1025", "Bind interface and port, e.g. 0.0.0.0:1025 or just :1025")
|
flag.StringVar(&smtpbindaddr, "smtpbindaddr", "0.0.0.0:1025", "SMTP bind interface and port, e.g. 0.0.0.0:1025 or just :1025")
|
||||||
|
flag.StringVar(&httpbindaddr, "httpbindaddr", "0.0.0.0:8025", "HTTP bind interface and port, e.g. 0.0.0.0:8025 or just :8025")
|
||||||
flag.StringVar(&hostname, "hostname", "mailhog.example", "Hostname for EHLO/HELO response, e.g. mailhog.example")
|
flag.StringVar(&hostname, "hostname", "mailhog.example", "Hostname for EHLO/HELO response, e.g. mailhog.example")
|
||||||
|
flag.StringVar(&mongouri, "mongouri", "127.0.0.1:27017", "MongoDB URI, e.g. 127.0.0.1:27017")
|
||||||
|
flag.StringVar(&mongodb, "mongodb", "mailhog", "MongoDB database, e.g. mailhog")
|
||||||
|
flag.StringVar(&mongocoll, "mongocoll", "messages", "MongoDB collection, e.g. messages")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
conf = &mailhog.Config{
|
conf = &mailhog.Config{
|
||||||
BindAddr: listen,
|
SMTPBindAddr: smtpbindaddr,
|
||||||
|
HTTPBindAddr: httpbindaddr,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
|
MongoUri: mongouri,
|
||||||
|
MongoDb: mongodb,
|
||||||
|
MongoColl: mongocoll,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config()
|
config()
|
||||||
|
|
||||||
ln := listen(conf.BindAddr)
|
exitCh = make(chan int)
|
||||||
|
go web_listen()
|
||||||
|
go smtp_listen()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-exitCh:
|
||||||
|
log.Printf("Received exit signal")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func web_listen() {
|
||||||
|
log.Printf("[HTTP] Binding to address: %s\n", conf.HTTPBindAddr)
|
||||||
|
http.Start(exitCh, conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func smtp_listen() (*net.TCPListener) {
|
||||||
|
log.Printf("[SMTP] Binding to address: %s\n", conf.SMTPBindAddr)
|
||||||
|
ln, err := net.Listen("tcp", conf.SMTPBindAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("[SMTP] Error listening on socket: %s\n", err)
|
||||||
|
}
|
||||||
defer ln.Close()
|
defer ln.Close()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := ln.Accept()
|
conn, err := ln.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error accepting connection: %s\n", err)
|
log.Printf("[SMTP] Error accepting connection: %s\n", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
@ -41,12 +75,3 @@ func main() {
|
||||||
go smtp.StartSession(conn.(*net.TCPConn), conf)
|
go smtp.StartSession(conn.(*net.TCPConn), conf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listen(bind string) (*net.TCPListener) {
|
|
||||||
log.Printf("Binding to address: %s\n", bind)
|
|
||||||
ln, err := net.Listen("tcp", bind)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error listening on socket: %s\n", err)
|
|
||||||
}
|
|
||||||
return ln.(*net.TCPListener)
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"github.com/ian-kent/MailHog/mailhog"
|
||||||
"github.com/ian-kent/MailHog/mailhog/storage"
|
"github.com/ian-kent/MailHog/mailhog/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ func TestBasicHappyPath(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, string(buf[0:n]), "221 Bye\n")
|
assert.Equal(t, string(buf[0:n]), "221 Bye\n")
|
||||||
|
|
||||||
message, err := storage.Load(match[1])
|
message, err := storage.Load(mailhog.DefaultConfig(), match[1])
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.NotNil(t, message)
|
assert.NotNil(t, message)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue