Refactor ADIF processing and improve tests

pull/41/head
Jean-Marc MEESSEN 4 years ago committed by GitHub
parent fd7902bdfc
commit e31e692c76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,6 +19,7 @@ limitations under the License.
import (
"FLEcli/fleprocess"
"fmt"
"os"
"github.com/spf13/cobra"
)
@ -49,13 +50,18 @@ var adifCmd = &cobra.Command{
return fmt.Errorf("Too many arguments.%s", "")
}
fleprocess.ProcessAdifCommand(
err := fleprocess.ProcessAdifCommand(
inputFilename,
outputFilename,
isInterpolateTime,
isWWFFcli,
isSOTAcli,
isOverwrite)
if err != nil {
fmt.Println("\nUnable to generate ADIF file:")
fmt.Println(err)
os.Exit(1)
}
return nil
},

@ -18,43 +18,114 @@ limitations under the License.
import (
"fmt"
"strings"
)
//ProcessAdifCommand FIXME
func ProcessAdifCommand(inputFilename, outputFilename string, isInterpolateTime, isWWFFcli, isSOTAcli, isOverwrite bool) {
//ProcessAdifCommand loads an FLE input to produce an adif file (eventually in WWFF format). It is called from the COBRA interface
func ProcessAdifCommand(inputFilename, outputFilename string, isInterpolateTime, isWWFFcli, isSOTAcli, isOverwrite bool) error {
verifiedOutputFilename, err := buildOutputFilename(outputFilename, inputFilename, isOverwrite, ".adi")
//Validate of build the output filenaem
var verifiedOutputFilename string
var err error
// if the output file could not be parsed correctly do noting
if err == nil {
loadedLogFile, isLoadedOK := LoadFile(inputFilename, isInterpolateTime)
if verifiedOutputFilename, err = buildOutputFilename(outputFilename, inputFilename, isOverwrite, ".adi"); err != nil {
return err
}
if isLoadedOK {
if len(loadedLogFile) == 0 {
fmt.Println("No useful data read. Aborting...")
return
}
//Load the input file
var loadedLogFile []LogLine
var isLoadedOK bool
if loadedLogFile, isLoadedOK = LoadFile(inputFilename, isInterpolateTime); isLoadedOK == false {
return fmt.Errorf("There were input file parsing errors. Could not generate ADIF file")
}
//Check if we have all the necessary data
if err := validateDataforAdif(loadedLogFile, isWWFFcli, isSOTAcli); err != nil {
return err
}
//Write the output file with the checked data
OutputAdif(verifiedOutputFilename, loadedLogFile, isWWFFcli, isSOTAcli)
//If we reached this point, everything was processed OK and the file generated
return nil
}
//validateDataforAdif checks whether all the required data is present
//The details of the mandatory files can be found at http://wwff.co/rules-faq/confirming-and-sending-log/
func validateDataforAdif(loadedLogFile []LogLine, isWWFFcli, isSOTAcli bool) error {
//do we have QSOs at all?
if len(loadedLogFile) == 0 {
return fmt.Errorf("No QSO found")
}
//MySOTA, MyWWFF and MyCall are header values. If missing on the first line, it will be missing at every line
if loadedLogFile[0].MyCall == "" {
return fmt.Errorf("Missing MyCall")
}
if isSOTAcli {
if loadedLogFile[0].MySOTA == "" {
return fmt.Errorf("Missing MY-SOTA reference")
}
}
if isWWFFcli {
if loadedLogFile[0].MyWWFF == "" {
return fmt.Errorf("Missing MY-WWFF reference")
}
if loadedLogFile[0].Operator == "" {
return fmt.Errorf("Missing Operator call sign")
}
}
var errorsBuffer strings.Builder
//We accumulate the errors messages
for i := 0; i < len(loadedLogFile); i++ {
//TODO: There are more tests required here
//check if we have the necessary information for the type
if isWWFFcli {
if loadedLogFile[0].MyWWFF == "" {
fmt.Println("Missing MY-WWFF reference. Aborting...")
return
}
if loadedLogFile[0].Operator == "" {
fmt.Println("Missing Operator. Aborting...")
return
}
//Compute the error location for a meaning full error
var errorLocation string
if loadedLogFile[i].Time == "" {
errorLocation = fmt.Sprintf("for log entry #%d", i+1)
} else {
errorLocation = fmt.Sprintf("for log entry at %s (#%d)", loadedLogFile[i].Time, i+1)
}
if loadedLogFile[i].Date == "" {
if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", "))
}
if isSOTAcli {
if loadedLogFile[0].MySOTA == "" {
fmt.Println("Missing MY-SOTA reference. Aborting...")
return
}
errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation))
}
if loadedLogFile[i].Band == "" {
if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", "))
}
OutputAdif(verifiedOutputFilename, loadedLogFile, isWWFFcli, isSOTAcli)
errorsBuffer.WriteString(fmt.Sprintf("missing band %s", errorLocation))
}
if loadedLogFile[i].Mode == "" {
if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", "))
}
errorsBuffer.WriteString(fmt.Sprintf("missing mode %s", errorLocation))
}
if loadedLogFile[i].Call == "" {
if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", "))
}
errorsBuffer.WriteString(fmt.Sprintf("missing call %s", errorLocation))
}
if loadedLogFile[i].Time == "" {
if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", "))
}
errorsBuffer.WriteString(fmt.Sprintf("missing QSO time %s", errorLocation))
}
}
if errorsBuffer.String() != "" {
return fmt.Errorf(errorsBuffer.String())
}
//If we reached here, all is ok
return nil
}

@ -0,0 +1,149 @@
package fleprocess
import (
"fmt"
"testing"
)
func Test_validateDataforAdif(t *testing.T) {
type args struct {
loadedLogFile []LogLine
isWWFFcli bool
isSOTAcli bool
}
tests := []struct {
name string
args args
want error
}{
{
"Happy Case (no sota or wwff)",
args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{
{Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "time", Call: "call"},
{Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "time", Call: "call"},
{Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "time", Call: "call"}},
},
nil,
},
{
"No data",
args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{}},
fmt.Errorf("No QSO found"),
},
{
"Missing Date",
args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{
{Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:01", Call: "call"},
{Date: "", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:02", Call: "call"},
{Date: "", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", Call: "call"}},
},
fmt.Errorf("missing date for log entry at 12:02 (#2), missing date for log entry at 12:03 (#3)"),
},
{
"Missing MyCall",
args{isWWFFcli: true, isSOTAcli: true, loadedLogFile: []LogLine{
{Date: "date", MyCall: "", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:01", Call: "call"},
{Date: "date", MyCall: "", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:02", Call: "call"},
{Date: "date", MyCall: "", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", Call: "call"}},
},
fmt.Errorf("Missing MyCall"),
},
{
"Missing MySota",
args{isWWFFcli: false, isSOTAcli: true, loadedLogFile: []LogLine{
{Date: "date", MyCall: "myCall", MySOTA: "", Mode: "mode", Band: "band", Time: "time", Call: "call"},
{Date: "date", MyCall: "myCall", MySOTA: "", Mode: "mode", Band: "band", Time: "time", Call: "call"},
{Date: "date", MyCall: "myCall", MySOTA: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}},
},
fmt.Errorf("Missing MY-SOTA reference"),
},
{
"Misc. missing data (Band, Time, Mode, Call)",
args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{
{Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "", Time: "", Call: "call"},
{Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "", Band: "band", Time: "12:02", Call: "call"},
{Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", Call: ""}},
},
fmt.Errorf("missing band for log entry #1, missing QSO time for log entry #1, missing mode for log entry at 12:02 (#2), missing call for log entry at 12:03 (#3)"),
},
{
"Missing MY-WWFF",
args{isWWFFcli: true, isSOTAcli: false, loadedLogFile: []LogLine{
{Date: "date", MyCall: "myCall", MySOTA: "mySota", MyWWFF: "", Mode: "mode", Band: "band", Time: "time", Call: "call"},
{Date: "date", MyCall: "myCall", MySOTA: "mySota", MyWWFF: "", Mode: "mode", Band: "band", Time: "time", Call: "call"},
{Date: "date", MyCall: "myCall", MySOTA: "mySota", MyWWFF: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}},
},
fmt.Errorf("Missing MY-WWFF reference"),
},
{
"Missing MY-WWFF",
args{isWWFFcli: true, isSOTAcli: false, loadedLogFile: []LogLine{
{Date: "date", MyCall: "myCall", MyWWFF: "myWwff", Operator: "", Mode: "mode", Band: "band", Time: "time", Call: "call"},
{Date: "date", MyCall: "myCall", MyWWFF: "myWwff", Operator: "", Mode: "mode", Band: "band", Time: "time", Call: "call"},
{Date: "date", MyCall: "myCall", MyWWFF: "myWwff", Operator: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}},
},
fmt.Errorf("Missing Operator call sign"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := validateDataforAdif(tt.args.loadedLogFile, tt.args.isWWFFcli, tt.args.isSOTAcli)
//Test the error message, if any
if got != nil && tt.want != nil {
if got.Error() != tt.want.Error() {
t.Errorf("validateDataforAdif() = %v, want %v", got, tt.want)
}
} else {
if !(got == nil && tt.want == nil) {
t.Errorf("validateDataforAdif() = %v, want %v", got, tt.want)
}
}
})
}
}
func TestProcessAdifCommand(t *testing.T) {
type args struct {
inputFilename string
outputFilename string
isInterpolateTime bool
isWWFFcli bool
isSOTAcli bool
isOverwrite bool
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"Bad output filename (directory)",
args{inputFilename: "../test/data/fle-4-no-qso.txt", outputFilename: "../test/data", isInterpolateTime: false, isOverwrite: false},
true,
},
{
"input file parsing errors (missing band)",
args{inputFilename: "../test/data/fle-3-error.txt", outputFilename: "", isInterpolateTime: false, isOverwrite: false},
true,
},
{
"input file parsing errors (wrong call)",
args{inputFilename: "../test/data/fle-5-wrong-call.txt", outputFilename: "", isInterpolateTime: false, isOverwrite: false},
true,
},
{
"No QSO in loaded file",
args{inputFilename: "../test/data/fle-4-no-qso.txt", outputFilename: "", isInterpolateTime: false, isOverwrite: false},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := ProcessAdifCommand(tt.args.inputFilename, tt.args.outputFilename, tt.args.isInterpolateTime, tt.args.isWWFFcli, tt.args.isSOTAcli, tt.args.isOverwrite); (err != nil) != tt.wantErr {
t.Errorf("ProcessCsvCommand() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

@ -24,13 +24,13 @@ import (
)
//ProcessCsvCommand loads an FLE input to produce a SOTA CSV
func ProcessCsvCommand(inputFilename, outputCsvFilename string, isInterpolateTime, isOverwriteCsv bool) error {
func ProcessCsvCommand(inputFilename, outputFilename string, isInterpolateTime, isOverwriteCsv bool) error {
//Validate of build the output filenaem
var verifiedOutputFilename string
var err error
if verifiedOutputFilename, err = buildOutputFilename(outputCsvFilename, inputFilename, isOverwriteCsv, ".csv"); err != nil {
if verifiedOutputFilename, err = buildOutputFilename(outputFilename, inputFilename, isOverwriteCsv, ".csv"); err != nil {
return err
}
@ -51,10 +51,9 @@ func ProcessCsvCommand(inputFilename, outputCsvFilename string, isInterpolateTim
return nil
}
//validateDataForSotaCsv checks whether all the requiered data is present in the supplied data
func validateDataForSotaCsv(loadedLogFile []LogLine) error {
if len(loadedLogFile) == 0 {
return fmt.Errorf("No QSO found")

@ -63,18 +63,54 @@ func Test_validateDataForSotaCsv(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := validateDataForSotaCsv(tt.args.loadedLogFile)
if tt.want == nil || got == nil {
if tt.want == nil && got != nil {
t.Errorf("validateDataForSotaCsv() = %v, want %v", got, nil)
}
if tt.want != nil && got == nil {
t.Errorf("validateDataForSotaCsv() = %v, want %v", nil, tt.want)
//Test the error message, if any
if got != nil && tt.want != nil {
if got.Error() != tt.want.Error() {
t.Errorf("validateDataForSotaCsv() = %v, want %v", got, tt.want)
}
} else {
if got.Error() != tt.want.Error() {
if !(got == nil && tt.want == nil) {
t.Errorf("validateDataForSotaCsv() = %v, want %v", got, tt.want)
}
}
})
}
}
func TestProcessCsvCommand(t *testing.T) {
type args struct {
inputFilename string
outputCsvFilename string
isInterpolateTime bool
isOverwriteCsv bool
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"Bad output filename (directory)",
args{inputFilename: "../test/data/fle-4-no-qso.txt", outputCsvFilename: "../test/data", isInterpolateTime: false, isOverwriteCsv: false},
true,
},
{
"input file parsing errors",
args{inputFilename: "../test/data/fle-3-error.txt", outputCsvFilename: "", isInterpolateTime: false, isOverwriteCsv: false},
true,
},
{
"No QSO in loaded file",
args{inputFilename: "../test/data/fle-4-no-qso.txt", outputCsvFilename: "", isInterpolateTime: false, isOverwriteCsv: false},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := ProcessCsvCommand(tt.args.inputFilename, tt.args.outputCsvFilename, tt.args.isInterpolateTime, tt.args.isOverwriteCsv); (err != nil) != tt.wantErr {
t.Errorf("ProcessCsvCommand() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

@ -55,4 +55,3 @@ func buildOutputFilename(output string, input string, overwrite bool, newExtensi
return "", fmt.Errorf("File already exists. Use --overwrite flag if necessary")
}

@ -103,15 +103,18 @@ func Test_buildOutputFilename(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotOutputFilename, gotErr := buildOutputFilename(tt.args.output, tt.args.input, tt.args.overwrite, tt.args.extension)
if gotOutputFilename != tt.wantOutputFilename {
t.Errorf("buildOutputFilename() gotOutputFilename = %v, want %v", gotOutputFilename, tt.wantOutputFilename)
}
//test the error message, if any
if gotErr != nil && tt.wantError != nil {
if gotErr.Error() != tt.wantError.Error() {
t.Errorf("buildOutputFilename() error = %v, want %v", gotErr, tt.wantError)
}
} else {
if!(gotErr == nil && tt.wantError == nil) {
if !(gotErr == nil && tt.wantError == nil) {
t.Errorf("buildOutputFilename() error = %v, want %v", gotErr, tt.wantError)
}
}

@ -0,0 +1,12 @@
date 2020-07-19
mycall ON6ZQ/P
mysota ON/ON-018
#wrong call
cw
30m
1150
55 ix1ihr
onyz4/f8dgf
56 ea2dt
Loading…
Cancel
Save