mirror of
https://github.com/bettercap/bettercap
synced 2025-08-24 15:16:18 -07:00
Compare commits
18 commits
Author | SHA1 | Date | |
---|---|---|---|
|
1001cea8f2 | ||
|
204997c8b5 | ||
|
4ec2753fad | ||
|
42da612113 | ||
|
fc65cde728 | ||
|
cc475ddfba | ||
|
cfc6d55462 | ||
|
ccf4fa09e2 | ||
|
1e235181aa | ||
|
453c417e92 |
||
|
d1925cd926 |
||
|
d60d4612f2 |
||
|
8bd6052851 |
||
|
a23ba5fcba |
||
|
be76c0a7da |
||
|
faee64a2c0 | ||
|
0f68fcca8b | ||
|
5a6a5fbbdf |
12 changed files with 218 additions and 55 deletions
2
.github/workflows/build-and-deploy.yml
vendored
2
.github/workflows/build-and-deploy.yml
vendored
|
@ -99,7 +99,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download Artifacts
|
- name: Download Artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
pattern: release-artifacts-*
|
pattern: release-artifacts-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
<a href="https://github.com/bettercap/bettercap/actions/workflows/test-on-macos.yml"><img alt="Tests on macOS" src="https://github.com/bettercap/bettercap/actions/workflows/test-on-macos.yml/badge.svg"></a>
|
<a href="https://github.com/bettercap/bettercap/actions/workflows/test-on-macos.yml"><img alt="Tests on macOS" src="https://github.com/bettercap/bettercap/actions/workflows/test-on-macos.yml/badge.svg"></a>
|
||||||
<a href="https://github.com/bettercap/bettercap/actions/workflows/test-on-windows.yml"><img alt="Tests on Windows" src="https://github.com/bettercap/bettercap/actions/workflows/test-on-windows.yml/badge.svg"></a>
|
<a href="https://github.com/bettercap/bettercap/actions/workflows/test-on-windows.yml"><img alt="Tests on Windows" src="https://github.com/bettercap/bettercap/actions/workflows/test-on-windows.yml/badge.svg"></a>
|
||||||
<a href="https://hub.docker.com/r/bettercap/bettercap"><img alt="Docker Hub" src="https://img.shields.io/docker/v/bettercap/bettercap?logo=docker"></a>
|
<a href="https://hub.docker.com/r/bettercap/bettercap"><img alt="Docker Hub" src="https://img.shields.io/docker/v/bettercap/bettercap?logo=docker"></a>
|
||||||
|
<img src="https://img.shields.io/badge/human-coded-brightgreen?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1wZXJzb24tc3RhbmRpbmctaWNvbiBsdWNpZGUtcGVyc29uLXN0YW5kaW5nIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjUiIHI9IjEiLz48cGF0aCBkPSJtOSAyMCAzLTYgMyA2Ii8+PHBhdGggZD0ibTYgOCA2IDIgNi0yIi8+PHBhdGggZD0iTTEyIDEwdjQiLz48L3N2Zz4=" alt="This project is 100% made by humans."/>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package core
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Name = "bettercap"
|
Name = "bettercap"
|
||||||
Version = "2.41.1"
|
Version = "2.41.4"
|
||||||
Author = "Simone 'evilsocket' Margaritelli"
|
Author = "Simone 'evilsocket' Margaritelli"
|
||||||
Website = "https://bettercap.org/"
|
Website = "https://bettercap.org/"
|
||||||
)
|
)
|
||||||
|
|
|
@ -90,12 +90,12 @@ func NewRestAPI(s *session.Session) *RestAPI {
|
||||||
"Value of the Access-Control-Allow-Origin header of the API server."))
|
"Value of the Access-Control-Allow-Origin header of the API server."))
|
||||||
|
|
||||||
mod.AddParam(session.NewStringParameter("api.rest.username",
|
mod.AddParam(session.NewStringParameter("api.rest.username",
|
||||||
"",
|
"user",
|
||||||
"",
|
"",
|
||||||
"API authentication username."))
|
"API authentication username."))
|
||||||
|
|
||||||
mod.AddParam(session.NewStringParameter("api.rest.password",
|
mod.AddParam(session.NewStringParameter("api.rest.password",
|
||||||
"",
|
"pass",
|
||||||
"",
|
"",
|
||||||
"API authentication password."))
|
"API authentication password."))
|
||||||
|
|
||||||
|
|
|
@ -31,20 +31,20 @@ func NewHttpServer(s *session.Session) *HttpServer {
|
||||||
mod.AddParam(session.NewStringParameter("http.server.address",
|
mod.AddParam(session.NewStringParameter("http.server.address",
|
||||||
session.ParamIfaceAddress,
|
session.ParamIfaceAddress,
|
||||||
session.IPv4Validator,
|
session.IPv4Validator,
|
||||||
"Address to bind the http server to."))
|
"Address to bind the HTTP server to."))
|
||||||
|
|
||||||
mod.AddParam(session.NewIntParameter("http.server.port",
|
mod.AddParam(session.NewIntParameter("http.server.port",
|
||||||
"80",
|
"80",
|
||||||
"Port to bind the http server to."))
|
"Port to bind the HTTP server to."))
|
||||||
|
|
||||||
mod.AddHandler(session.NewModuleHandler("http.server on", "",
|
mod.AddHandler(session.NewModuleHandler("http.server on", "",
|
||||||
"Start httpd server.",
|
"Start HTTP server.",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
return mod.Start()
|
return mod.Start()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mod.AddHandler(session.NewModuleHandler("http.server off", "",
|
mod.AddHandler(session.NewModuleHandler("http.server off", "",
|
||||||
"Stop httpd server.",
|
"Stop HTTP server.",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
return mod.Stop()
|
return mod.Stop()
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -35,11 +35,11 @@ func NewHttpsServer(s *session.Session) *HttpsServer {
|
||||||
mod.AddParam(session.NewStringParameter("https.server.address",
|
mod.AddParam(session.NewStringParameter("https.server.address",
|
||||||
session.ParamIfaceAddress,
|
session.ParamIfaceAddress,
|
||||||
session.IPv4Validator,
|
session.IPv4Validator,
|
||||||
"Address to bind the http server to."))
|
"Address to bind the HTTPS server to."))
|
||||||
|
|
||||||
mod.AddParam(session.NewIntParameter("https.server.port",
|
mod.AddParam(session.NewIntParameter("https.server.port",
|
||||||
"443",
|
"443",
|
||||||
"Port to bind the http server to."))
|
"Port to bind the HTTPS server to."))
|
||||||
|
|
||||||
mod.AddParam(session.NewStringParameter("https.server.certificate",
|
mod.AddParam(session.NewStringParameter("https.server.certificate",
|
||||||
"~/.bettercap-httpd.cert.pem",
|
"~/.bettercap-httpd.cert.pem",
|
||||||
|
@ -54,13 +54,13 @@ func NewHttpsServer(s *session.Session) *HttpsServer {
|
||||||
tls.CertConfigToModule("https.server", &mod.SessionModule, tls.DefaultLegitConfig)
|
tls.CertConfigToModule("https.server", &mod.SessionModule, tls.DefaultLegitConfig)
|
||||||
|
|
||||||
mod.AddHandler(session.NewModuleHandler("https.server on", "",
|
mod.AddHandler(session.NewModuleHandler("https.server on", "",
|
||||||
"Start https server.",
|
"Start HTTPS server.",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
return mod.Start()
|
return mod.Start()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mod.AddHandler(session.NewModuleHandler("https.server off", "",
|
mod.AddHandler(session.NewModuleHandler("https.server off", "",
|
||||||
"Stop https server.",
|
"Stop HTTPS server.",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
return mod.Stop()
|
return mod.Stop()
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -16,15 +16,13 @@ import (
|
||||||
"github.com/evilsocket/islazy/fs"
|
"github.com/evilsocket/islazy/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type hookFunc func(q *nfqueue.Nfqueue, a nfqueue.Attribute) int
|
|
||||||
|
|
||||||
type PacketProxy struct {
|
type PacketProxy struct {
|
||||||
session.SessionModule
|
session.SessionModule
|
||||||
chainName string
|
chainName string
|
||||||
rule string
|
rule string
|
||||||
queue *nfqueue.Nfqueue
|
queue *nfqueue.Nfqueue
|
||||||
queueNum int
|
queueNum int
|
||||||
queueCb hookFunc
|
queueCb func(q *nfqueue.Nfqueue, a nfqueue.Attribute) int
|
||||||
pluginPath string
|
pluginPath string
|
||||||
plugin *plugin.Plugin
|
plugin *plugin.Plugin
|
||||||
}
|
}
|
||||||
|
@ -151,7 +149,7 @@ func (mod *PacketProxy) Configure() (err error) {
|
||||||
return
|
return
|
||||||
} else if sym, err = mod.plugin.Lookup("OnPacket"); err != nil {
|
} else if sym, err = mod.plugin.Lookup("OnPacket"); err != nil {
|
||||||
return
|
return
|
||||||
} else if mod.queueCb, ok = sym.(hookFunc); !ok {
|
} else if mod.queueCb, ok = sym.(func(q *nfqueue.Nfqueue, a nfqueue.Attribute) int); !ok {
|
||||||
return fmt.Errorf("Symbol OnPacket is not a valid callback function.")
|
return fmt.Errorf("Symbol OnPacket is not a valid callback function.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package tcp_proxy
|
package tcp_proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -55,12 +56,36 @@ func (s *TcpProxyScript) OnData(from, to net.Addr, data []byte, callback func(ca
|
||||||
log.Error("error while executing onData callback: %s", err)
|
log.Error("error while executing onData callback: %s", err)
|
||||||
return nil
|
return nil
|
||||||
} else if ret != nil {
|
} else if ret != nil {
|
||||||
array, ok := ret.([]byte)
|
return toByteArray(ret)
|
||||||
if !ok {
|
|
||||||
log.Error("error while casting exported value to array of byte: value = %+v", ret)
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toByteArray(ret interface{}) []byte {
|
||||||
|
// this approach is a bit hacky but it handles all cases
|
||||||
|
|
||||||
|
// serialize ret to JSON
|
||||||
|
if jsonData, err := json.Marshal(ret); err == nil {
|
||||||
|
// attempt to deserialize as []float64
|
||||||
|
var back2Array []float64
|
||||||
|
if err := json.Unmarshal(jsonData, &back2Array); err == nil {
|
||||||
|
result := make([]byte, len(back2Array))
|
||||||
|
for i, num := range back2Array {
|
||||||
|
if num >= 0 && num <= 255 {
|
||||||
|
result[i] = byte(num)
|
||||||
|
} else {
|
||||||
|
log.Error("array element at index %d is not a valid byte value %d", i, num)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
log.Error("failed to deserialize %+v to []float64: %v", ret, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error("failed to serialize %+v to JSON: %v", ret, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
169
modules/tcp_proxy/tcp_proxy_script_test.go
Normal file
169
modules/tcp_proxy/tcp_proxy_script_test.go
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
package tcp_proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/evilsocket/islazy/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOnData_NoReturn(t *testing.T) {
|
||||||
|
jsCode := `
|
||||||
|
function onData(from, to, data, callback) {
|
||||||
|
// don't return anything
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
plug, err := plugin.Parse(jsCode)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse plugin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
script := &TcpProxyScript{
|
||||||
|
Plugin: plug,
|
||||||
|
doOnData: plug.HasFunc("onData"),
|
||||||
|
}
|
||||||
|
|
||||||
|
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
|
||||||
|
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.2"), Port: 5678}
|
||||||
|
data := []byte("test data")
|
||||||
|
|
||||||
|
result := script.OnData(from, to, data, nil)
|
||||||
|
if result != nil {
|
||||||
|
t.Errorf("Expected nil result when callback returns nothing, got %v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnData_ReturnsArrayOfIntegers(t *testing.T) {
|
||||||
|
jsCode := `
|
||||||
|
function onData(from, to, data, callback) {
|
||||||
|
// Return modified data as array of integers
|
||||||
|
return [72, 101, 108, 108, 111]; // "Hello" in ASCII
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
plug, err := plugin.Parse(jsCode)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse plugin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
script := &TcpProxyScript{
|
||||||
|
Plugin: plug,
|
||||||
|
doOnData: plug.HasFunc("onData"),
|
||||||
|
}
|
||||||
|
|
||||||
|
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
|
||||||
|
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.2"), Port: 5678}
|
||||||
|
data := []byte("test data")
|
||||||
|
|
||||||
|
result := script.OnData(from, to, data, nil)
|
||||||
|
expected := []byte("Hello")
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("Expected non-nil result when callback returns array of integers")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) != len(expected) {
|
||||||
|
t.Fatalf("Expected result length %d, got %d", len(expected), len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, b := range result {
|
||||||
|
if b != expected[i] {
|
||||||
|
t.Errorf("Expected byte at index %d to be %d, got %d", i, expected[i], b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnData_ReturnsDynamicArray(t *testing.T) {
|
||||||
|
jsCode := `
|
||||||
|
function onData(from, to, data, callback) {
|
||||||
|
var result = [];
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
result.push((data[i] + 1) % 256);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
plug, err := plugin.Parse(jsCode)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse plugin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
script := &TcpProxyScript{
|
||||||
|
Plugin: plug,
|
||||||
|
doOnData: plug.HasFunc("onData"),
|
||||||
|
}
|
||||||
|
|
||||||
|
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
|
||||||
|
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.2"), Port: 5678}
|
||||||
|
data := []byte{10, 20, 30, 40, 255}
|
||||||
|
|
||||||
|
result := script.OnData(from, to, data, nil)
|
||||||
|
expected := []byte{11, 21, 31, 41, 0} // 255 + 1 = 256 % 256 = 0
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("Expected non-nil result when callback returns array of integers")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) != len(expected) {
|
||||||
|
t.Fatalf("Expected result length %d, got %d", len(expected), len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, b := range result {
|
||||||
|
if b != expected[i] {
|
||||||
|
t.Errorf("Expected byte at index %d to be %d, got %d", i, expected[i], b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnData_ReturnsMixedArray(t *testing.T) {
|
||||||
|
jsCode := `
|
||||||
|
function charToInt(value) {
|
||||||
|
return value.charCodeAt()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onData(from, to, data) {
|
||||||
|
st_data = String.fromCharCode.apply(null, data)
|
||||||
|
if( st_data.indexOf("mysearch") != -1 ) {
|
||||||
|
payload = "mypayload";
|
||||||
|
st_data = st_data.replace("mysearch", payload);
|
||||||
|
res_int_arr = st_data.split("").map(charToInt) // []uint16
|
||||||
|
res_int_arr[0] = payload.length + 1; // first index is float64 and rest []uint16
|
||||||
|
return res_int_arr;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
plug, err := plugin.Parse(jsCode)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse plugin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
script := &TcpProxyScript{
|
||||||
|
Plugin: plug,
|
||||||
|
doOnData: plug.HasFunc("onData"),
|
||||||
|
}
|
||||||
|
|
||||||
|
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
|
||||||
|
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.6"), Port: 5678}
|
||||||
|
data := []byte("Hello mysearch world")
|
||||||
|
|
||||||
|
result := script.OnData(from, to, data, nil)
|
||||||
|
expected := []byte("\x0aello mypayload world")
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("Expected non-nil result when callback returns array of integers")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) != len(expected) {
|
||||||
|
t.Fatalf("Expected result length %d, got %d", len(expected), len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, b := range result {
|
||||||
|
if b != expected[i] {
|
||||||
|
t.Errorf("Expected byte at index %d to be %d, got %d", i, expected[i], b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ func NewTicker(s *session.Session) *Ticker {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mod.AddHandler(session.NewModuleHandler("ticker off", "",
|
mod.AddHandler(session.NewModuleHandler("ticker off", "",
|
||||||
"Stop the maint icker.",
|
"Stop the main ticker.",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
return mod.Stop()
|
return mod.Stop()
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -265,8 +265,8 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
|
||||||
|
|
||||||
mod.AddHandler(probe)
|
mod.AddHandler(probe)
|
||||||
|
|
||||||
channelSwitchAnnounce := session.NewModuleHandler("wifi.channel_switch_announce bssid channel ", `wifi\.channel_switch_announce ((?:[a-fA-F0-9:]{11,}))\s+((?:[0-9]+))`,
|
channelSwitchAnnounce := session.NewModuleHandler("wifi.channel_switch_announce BSSID CHANNEL ", `wifi\.channel_switch_announce ((?:[a-fA-F0-9:]{11,}))\s+((?:[0-9]+))`,
|
||||||
"Start a 802.11 channel hop attack, all client will be force to change the channel lead to connection down.",
|
"Start a 802.11 channel hop attack, all client will be forced to change the channel lead to connection down.",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
bssid, err := net.ParseMAC(args[0])
|
bssid, err := net.ParseMAC(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -518,37 +518,6 @@ func TestWiFiModuleProbe(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWiFiModuleChannelSwitchAnnounce(t *testing.T) {
|
|
||||||
sess := createMockSession()
|
|
||||||
mod := NewWiFiModule(sess)
|
|
||||||
|
|
||||||
// Test CSA handler
|
|
||||||
handlers := mod.Handlers()
|
|
||||||
var csaHandler session.ModuleHandler
|
|
||||||
for _, h := range handlers {
|
|
||||||
if h.Name == "wifi.channel_switch_announce bssid channel " {
|
|
||||||
csaHandler = h
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if csaHandler.Name == "" {
|
|
||||||
t.Fatal("CSA handler not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with valid parameters
|
|
||||||
err := csaHandler.Exec([]string{"aa:bb:cc:dd:ee:ff", "11"})
|
|
||||||
if err == nil {
|
|
||||||
t.Error("Expected error when running CSA without running module")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with invalid channel
|
|
||||||
err = csaHandler.Exec([]string{"aa:bb:cc:dd:ee:ff", "999"})
|
|
||||||
if err == nil {
|
|
||||||
t.Error("Expected error with invalid channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWiFiModuleFakeAuth(t *testing.T) {
|
func TestWiFiModuleFakeAuth(t *testing.T) {
|
||||||
sess := createMockSession()
|
sess := createMockSession()
|
||||||
mod := NewWiFiModule(sess)
|
mod := NewWiFiModule(sess)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue