diff --git a/broker/fastexact.go b/broker/fastexact.go index 7ef26ea..dd1a77d 100644 --- a/broker/fastexact.go +++ b/broker/fastexact.go @@ -1,9 +1,15 @@ package broker import ( + "bytes" "errors" + "fmt" + "io" + "io/ioutil" + "local/storage" "local/truckstop/config" "net/http" + "strings" ) type FastExact struct { @@ -35,35 +41,100 @@ func (fe FastExact) Search(states []config.State) ([]Job, error) { } func (fe FastExact) login() error { - return errors.New("not impl") + conf := config.Get() + return fe._login(conf.Brokers.FastExact.Username, conf.Brokers.FastExact.Password, conf.DB()) +} + +func (fe FastExact) _login(username, password string, db storage.DB) error { + req, err := http.NewRequest( + http.MethodPost, + `https://www.fastexact.com/secure/index.php?page=userLogin`, + strings.NewReader(fmt.Sprintf( + `user_name=%s&user_password=%s&buttonSubmit=Login`, + username, + password, + )), + ) + if err != nil { + return err + } + fe.setHeaders(req) + + db.Set("cookies_"+req.URL.Host, nil) + resp, err := fe.doer.doRequest(req) + if err != nil { + return err + } + b, _ := ioutil.ReadAll(resp.Body) + resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("bad status logging into fast exact: %d: %s", resp.StatusCode, b) + } + return nil +} + +func (fe FastExact) setHeaders(req *http.Request) { + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0") + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") + req.Header.Set("Accept-Language", "en-US,en;q=0.5") + req.Header.Set("Accept-Encoding", "gzip, deflate, br") + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } func (fe FastExact) search(states []config.State) ([]Job, error) { - req, err := fe.newRequest(states) + var result []Job + for _, state := range states { + jobs, err := fe.searchOne(state) + if err != nil { + return nil, err + } + result = append(result, jobs...) + } + return result, nil +} + +func (fe FastExact) searchOne(state config.State) ([]Job, error) { + req, err := fe.newRequest(state) if err != nil { return nil, err } - resp, err := fe.doRequest(req) + resp, err := fe.doer.doRequest(req) if err != nil { return nil, err } return fe.parse(resp) } -func (fe FastExact) newRequest(states []config.State) (*http.Request, error) { - return nil, errors.New("not impl") +func (fe FastExact) newRequest(state config.State) (*http.Request, error) { + return nil, errors.New("not impl: fe.newreq") } func (fe FastExact) doRequest(req *http.Request) (*http.Response, error) { - return nil, errors.New("not impl") + return nil, errors.New("not impl: fe.doreq") } func (fe FastExact) parse(resp *http.Response) ([]Job, error) { - return nil, errors.New("not impl") + return nil, errors.New("not impl: fe.parse") } type mockFastExactDoer struct{} -func (mock mockFastExactDoer) doRequest(*http.Request) (*http.Response, error) { - return nil, errors.New("not impl") +func (mock mockFastExactDoer) doRequest(req *http.Request) (*http.Response, error) { + switch req.URL.Path { + case "/secure/index.php": + if req.URL.Query().Get("page") != "userLogin" { + return nil, errors.New("bad query") + } + if b, _ := ioutil.ReadAll(req.Body); !bytes.Equal(b, []byte(`user_name=u&user_password=p&buttonSubmit=Login`)) { + return nil, errors.New("bad req body") + } + return &http.Response{ + Status: http.StatusText(http.StatusOK), + StatusCode: http.StatusOK, + Header: http.Header{"Set-Cookie": []string{"PHPSESSID=8o0slo18q6cr0v5v5k9ohjcg01; path=/"}}, + Body: io.NopCloser(bytes.NewReader([]byte{})), + }, nil + } + return nil, errors.New("bad path") } diff --git a/broker/fastexact_test.go b/broker/fastexact_test.go new file mode 100644 index 0000000..d0533c7 --- /dev/null +++ b/broker/fastexact_test.go @@ -0,0 +1,32 @@ +package broker + +import ( + "local/storage" + "local/truckstop/config" + "testing" + + "golang.org/x/time/rate" +) + +func TestFastExactLogin(t *testing.T) { + limiter = rate.NewLimiter(rate.Limit(60.0), 1) + fe := NewFastExact().WithMock() + _ = fe + db := storage.NewMap() + if err := fe._login("u", "p", db); err != nil { + t.Fatal(err) + } +} + +func TestFastExactSearch(t *testing.T) { + limiter = rate.NewLimiter(rate.Limit(60.0), 1) + fe := NewFastExact().WithMock() + _ = fe + db := storage.NewMap() + _ = db + if jobs, err := fe.search([]config.State{config.State("NC"), config.State("SC")}); err != nil { + t.Fatal(err) + } else { + t.Fatal(jobs) + } +}