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

@ -18,43 +18,114 @@ limitations under the License.
import ( import (
"fmt" "fmt"
"strings"
) )
//ProcessAdifCommand FIXME //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) { 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 verifiedOutputFilename, err = buildOutputFilename(outputFilename, inputFilename, isOverwrite, ".adi"); err != nil {
if err == nil { return err
loadedLogFile, isLoadedOK := LoadFile(inputFilename, isInterpolateTime) }
//Load the input file
var loadedLogFile []LogLine
var isLoadedOK bool
if isLoadedOK { 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 { if len(loadedLogFile) == 0 {
fmt.Println("No useful data read. Aborting...") return fmt.Errorf("No QSO found")
return
} }
//TODO: There are more tests required here //MySOTA, MyWWFF and MyCall are header values. If missing on the first line, it will be missing at every line
//check if we have the necessary information for the type 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 isWWFFcli {
if loadedLogFile[0].MyWWFF == "" { if loadedLogFile[0].MyWWFF == "" {
fmt.Println("Missing MY-WWFF reference. Aborting...") return fmt.Errorf("Missing MY-WWFF reference")
return
} }
if loadedLogFile[0].Operator == "" { if loadedLogFile[0].Operator == "" {
fmt.Println("Missing Operator. Aborting...") return fmt.Errorf("Missing Operator call sign")
return
} }
} }
if isSOTAcli {
if loadedLogFile[0].MySOTA == "" { var errorsBuffer strings.Builder
fmt.Println("Missing MY-SOTA reference. Aborting...") //We accumulate the errors messages
return for i := 0; i < len(loadedLogFile); i++ {
}
//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)
} }
OutputAdif(verifiedOutputFilename, loadedLogFile, isWWFFcli, isSOTAcli) if loadedLogFile[i].Date == "" {
if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", "))
}
errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation))
}
if loadedLogFile[i].Band == "" {
if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", "))
}
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 //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 //Validate of build the output filenaem
var verifiedOutputFilename string var verifiedOutputFilename string
var err error 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 return err
} }
@ -51,10 +51,9 @@ func ProcessCsvCommand(inputFilename, outputCsvFilename string, isInterpolateTim
return nil return nil
} }
//validateDataForSotaCsv checks whether all the requiered data is present in the supplied data
func validateDataForSotaCsv(loadedLogFile []LogLine) error { func validateDataForSotaCsv(loadedLogFile []LogLine) error {
if len(loadedLogFile) == 0 { if len(loadedLogFile) == 0 {
return fmt.Errorf("No QSO found") return fmt.Errorf("No QSO found")

@ -63,18 +63,54 @@ func Test_validateDataForSotaCsv(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got := validateDataForSotaCsv(tt.args.loadedLogFile) got := validateDataForSotaCsv(tt.args.loadedLogFile)
if tt.want == nil || got == nil {
if tt.want == nil && got != nil { //Test the error message, if any
t.Errorf("validateDataForSotaCsv() = %v, want %v", got, nil) if got != nil && tt.want != nil {
} if got.Error() != tt.want.Error() {
if tt.want != nil && got == nil { t.Errorf("validateDataForSotaCsv() = %v, want %v", got, tt.want)
t.Errorf("validateDataForSotaCsv() = %v, want %v", nil, tt.want)
} }
} else { } else {
if got.Error() != tt.want.Error() { if !(got == nil && tt.want == nil) {
t.Errorf("validateDataForSotaCsv() = %v, want %v", got, tt.want) 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") return "", fmt.Errorf("File already exists. Use --overwrite flag if necessary")
} }

@ -103,9 +103,12 @@ func Test_buildOutputFilename(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
gotOutputFilename, gotErr := buildOutputFilename(tt.args.output, tt.args.input, tt.args.overwrite, tt.args.extension) gotOutputFilename, gotErr := buildOutputFilename(tt.args.output, tt.args.input, tt.args.overwrite, tt.args.extension)
if gotOutputFilename != tt.wantOutputFilename { if gotOutputFilename != tt.wantOutputFilename {
t.Errorf("buildOutputFilename() gotOutputFilename = %v, want %v", 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 != nil && tt.wantError != nil {
if gotErr.Error() != tt.wantError.Error() { if gotErr.Error() != tt.wantError.Error() {
t.Errorf("buildOutputFilename() error = %v, want %v", gotErr, tt.wantError) 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