Sfoglia il codice sorgente

Working alias generation, inserts into DB, some integration tests.

Frederic G. MARAND 10 anni fa
parent
commit
ecf5044df5
12 ha cambiato i file con 195 aggiunte e 20 eliminazioni
  1. 3 0
      .gitignore
  2. 35 3
      README.md
  3. 3 3
      doc/model.dot
  4. 2 2
      doc/model.svg
  5. 4 0
      storage/info.go
  6. 4 1
      storage/user.go
  7. 2 2
      strategy/hexcrc32.go
  8. 1 1
      strategy/manual.go
  9. 32 8
      strategy/strategy.go
  10. 97 0
      strategy/strategy_test.go
  11. 11 0
      url/longurl.go
  12. 1 0
      url/shorturl.go

+ 3 - 0
.gitignore

@@ -6,6 +6,9 @@
 *.a
 *.so
 
+# Coverage results
+coverage.out
+
 # Folders
 _obj
 _test

+ 35 - 3
README.md

@@ -1,15 +1,47 @@
+
+     #    #  #    #  #####   ######
+     #   #   #    #  #    #      #
+     ####    #    #  #    #     #
+     #  #    #    #  #####     #
+     #   #   #    #  #   #    #
+     #    #   ####   #    #  ######
+
 _Kurz_ is yet another URL shortener/aliaser.
 
-  * built as a Go API, for internal site use as well as public use
-  * web front end provided
+  * built as a Go data model API, for internal site use as well as public use
+  * web front end separate from data logic
   * multiple aliasing strategies. provided:
+    * identity
     * 32-bits hexa string, leading 0s omitted
-    * base36
     * manual selection
+    * easy to extend, by registering additional AliasStrategy objects
   * supporting
     * vanity domains
     * relative paths (API only, no Web UI), for use within web sites
     * "slug"-type multipart aliasing for SEO
   * usage statistics
+  * some tests available
 
 It is currently available under the General Public License version 3 or later.
+
+
+Using Kurz
+==========
+
+The main Kurz command show how to initialize the package and access statistics.
+
+You can find usage examples in strategy_test.go:
+
+- TestBaseAlias() shows how to generate aliases
+- TestUseCounts() show how to query strategy statistics
+
+
+Running tests
+=============
+
+Currently, only the strategy package has tests, and these need some setup, as
+they touch the database:
+
+- either redefine the credentials in strategy_test.go#initTestStorage()
+- or create a database with privileges appropriate for them
+- or send a PR submitting a better DB initialization mechanism for tests

+ 3 - 3
doc/model.dot

@@ -9,7 +9,7 @@ digraph kurz {
   language [ label="<k>Language|native name" ];
   likings [ label="<k>Likings|<user>user|<long>longUrl|ts" ];
   long [ label="<k>Long URL|value" ];
-  longmeta [ label="<k>Long metadata|<long>longUrl|mimeType|<language>language|imagePath|<info>origin" ];
+  longmeta [ label="<k>Long metadata|<url>url|mimeType|<language>language|imagePath|<info>origin" ];
   session [ label="<k>Session|<user>user|<info>info" ];
   short [ label="<k>Short URL|value|<long>shortFor|<domain>domain|<strategy>strategy|<user>submittedBy|submittedOn|isEnabled" ];
   strategy [ label="<k>Strategy|name" ];
@@ -27,10 +27,10 @@ digraph kurz {
 
   longmeta:info -> eventinfo:k;
   longmeta:language -> language:k;
-  longmeta:long -> long:k;
+  longmeta:url -> long:k;
 
   session:info -> eventinfo:k;
-  session:user -> user:k;  
+  session:user -> user:k;
 
   short:domain -> domain:k;
   short:long -> long:k;

+ 2 - 2
doc/model.svg

@@ -137,7 +137,7 @@
 <polygon fill="none" stroke="black" points="282.5,-52 282.5,-208 403.5,-208 403.5,-52 282.5,-52"/>
 <text text-anchor="middle" x="343" y="-191.4" font-family="Times Roman,serif" font-size="14.00">Long metadata</text>
 <polyline fill="none" stroke="black" points="282.5,-182 403.5,-182 "/>
-<text text-anchor="middle" x="343" y="-165.4" font-family="Times Roman,serif" font-size="14.00">longUrl</text>
+<text text-anchor="middle" x="343" y="-165.4" font-family="Times Roman,serif" font-size="14.00">url</text>
 <polyline fill="none" stroke="black" points="282.5,-156 403.5,-156 "/>
 <text text-anchor="middle" x="343" y="-139.4" font-family="Times Roman,serif" font-size="14.00">mimeType</text>
 <polyline fill="none" stroke="black" points="282.5,-130 403.5,-130 "/>
@@ -160,7 +160,7 @@
 <text text-anchor="middle" x="527" y="-183.4" font-family="Times Roman,serif" font-size="14.00">is a</text>
 </g>
 <!-- longmeta&#45;&gt;long -->
-<g id="edge18" class="edge"><title>longmeta:long&#45;&gt;long:k</title>
+<g id="edge18" class="edge"><title>longmeta:url&#45;&gt;long:k</title>
 <path fill="none" stroke="red" d="M404,-169C425.465,-169 452.6,-226.422 475.483,-240.777"/>
 <polygon fill="red" stroke="red" points="474.406,-244.108 485,-244 476.651,-237.478 474.406,-244.108"/>
 <text text-anchor="middle" x="434" y="-212.4" font-family="Times Roman,serif" font-size="14.00">is a</text>

+ 4 - 0
storage/info.go

@@ -0,0 +1,4 @@
+package storage
+
+type EventInfo struct {
+}

+ 4 - 1
storage/user.go

@@ -1,6 +1,9 @@
 package storage
 
-type User struct{}
+type User struct {
+	Id              int64
+	DefaultStrategy string // The name of the user's default strategy
+}
 
 func CurrentUser() User {
 	var ret User

+ 2 - 2
strategy/hexcrc32.go

@@ -19,11 +19,11 @@ type HexCrc32Strategy struct {
 	base baseStrategy
 }
 
-func (s HexCrc32Strategy) Name() string {
+func (y HexCrc32Strategy) Name() string {
 	return "hexCrc32"
 }
 
-func (s HexCrc32Strategy) Alias(short url.LongUrl, options ...interface{}) (url.ShortUrl, error) {
+func (y HexCrc32Strategy) Alias(short url.LongUrl, s storage.Storage, options ...interface{}) (url.ShortUrl, error) {
 	var ret url.ShortUrl
 
 	return ret, errors.New("HexCrc32 not implemented yet")

+ 1 - 1
strategy/manual.go

@@ -21,7 +21,7 @@ Alias() implements AliasingStrategy.Alias().
 
   - options is expected to be a non empty single string
 */
-func (y ManualStrategy) Alias(long url.LongUrl, options ...interface{}) (url.ShortUrl, error) {
+func (y ManualStrategy) Alias(long url.LongUrl, s storage.Storage, options ...interface{}) (url.ShortUrl, error) {
 	var ret url.ShortUrl
 	var err error
 	if len(options) != 1 {

+ 32 - 8
strategy/strategy.go

@@ -10,10 +10,10 @@ Files
 package strategy
 
 import (
-	"errors"
 	"github.com/FGM/kurz/storage"
 	"github.com/FGM/kurz/url"
 	"log"
+	"time"
 )
 
 /*
@@ -23,9 +23,9 @@ The options parameter for Alias() MAY be used by some strategies, in which case
 have to define their expectations about it.
 */
 type AliasingStrategy interface {
-	Name() string                                                        // Return the name of the strategy object
-	Alias(url url.LongUrl, options ...interface{}) (url.ShortUrl, error) // Return the short URL (alias) for a given long (source) URL
-	UseCount(storage storage.Storage) int                                // Return the number of short URLs (aliases) using this strategy.
+	Name() string                                                                           // Return the name of the strategy object
+	Alias(url url.LongUrl, s storage.Storage, options ...interface{}) (url.ShortUrl, error) // Return the short URL (alias) for a given long (source) URL
+	UseCount(storage storage.Storage) int                                                   // Return the number of short URLs (aliases) using this strategy.
 }
 
 type baseStrategy struct{}
@@ -34,11 +34,35 @@ func (y baseStrategy) Name() string {
 	return "base"
 }
 
-func (y baseStrategy) Alias(long url.LongUrl, options ...interface{}) (url.ShortUrl, error) {
-	var ret url.ShortUrl
-	var err error = errors.New("Base strategy is abstract")
+func (y baseStrategy) Alias(long url.LongUrl, s storage.Storage, options ...interface{}) (url.ShortUrl, error) {
+	var short url.ShortUrl
+	var err error
 
-	return ret, err
+	/** TODO
+	 * - validate alias is available
+	 */
+	short = url.ShortUrl{
+		Value:       long.Value,
+		ShortFor:    long,
+		Domain:      long.Domain(),
+		Strategy:    y.Name(),
+		SubmittedBy: storage.CurrentUser(),
+		SubmittedOn: time.Now().UTC().Unix(),
+		IsEnabled:   true,
+	}
+
+	sql := `
+		INSERT INTO shorturl(url, domain, strategy, submittedBy, submittedInfo, isEnabled)
+		VALUES (?, ?, ?, ?, ?, ?)
+		`
+	result, err := s.DB.Exec(sql, short.Value, short.Domain, short.Strategy, short.SubmittedBy.Id, short.SubmittedOn, short.IsEnabled)
+	if err != nil {
+		log.Printf("Failed inserting %s: %#V", short.Value, err)
+	} else {
+		short.Id, _ = result.LastInsertId()
+	}
+
+	return short, err
 }
 
 /**

+ 97 - 0
strategy/strategy_test.go

@@ -0,0 +1,97 @@
+package strategy
+
+import (
+	"github.com/FGM/kurz/storage"
+	"github.com/FGM/kurz/url"
+	"testing"
+)
+
+// FIXME stop using constant credentials
+func initTestStorage(t *testing.T) {
+	var DSN = "root:@tcp(localhost:3306)/go_kurz_test"
+	storage.Service.SetDSN(DSN)
+	err := storage.Service.Open()
+	if err != nil {
+		t.Fatalf("Failed opening the test database: %+V", err)
+	}
+}
+
+func TestBaseAlias(t *testing.T) {
+	const BASE = "base"
+
+	account := storage.User{
+		DefaultStrategy: BASE,
+	}
+	strategy := Strategies[account.DefaultStrategy]
+	if strategy.Name() != BASE {
+		t.Fatalf("Strategy: expected %s, got %s", BASE, strategy.Name())
+	}
+
+	initTestStorage(t)
+	defer storage.Service.Close()
+
+	sourceUrl := url.LongUrl{
+		Value: "http://www.example.com",
+	}
+	alias, err := strategy.Alias(sourceUrl, storage.Service)
+	if err != nil {
+		t.Errorf("Failed during Alias(): %+v", err)
+	}
+	if alias.ShortFor != sourceUrl {
+		t.Errorf("Aliasing does not point to proper long URL: expected %+V, got %+V", sourceUrl, alias.ShortFor)
+	}
+
+	if alias.Value != sourceUrl.Value {
+		t.Errorf("Aliasing does not build the proper URL: expected %+V, got %+V", sourceUrl.Value, alias.Value)
+	}
+
+	storage.Service.DB.Exec("TRUNCATE shorturl")
+}
+
+func TestUseCounts(t *testing.T) {
+	const BASE = "base"
+
+	account := storage.User{
+		DefaultStrategy: BASE,
+	}
+	strategy := Strategies[account.DefaultStrategy]
+	if strategy.Name() != BASE {
+		t.Fatalf("Strategy: expected %s, got %s", BASE, strategy.Name())
+	}
+
+	initTestStorage(t)
+	defer storage.Service.Close()
+
+	initialCount := strategy.UseCount(storage.Service)
+	if initialCount != 0 {
+		t.Errorf("Found %d record(s) in test database, expecting none.", initialCount)
+	}
+
+	sourceUrl := url.LongUrl{
+		Value: "http://www.example.com",
+	}
+	_, err := strategy.Alias(sourceUrl, storage.Service)
+	if err != nil {
+		t.Errorf("Failed during Alias(): %+v", err)
+	}
+
+	nextCount := strategy.UseCount(storage.Service)
+	if nextCount != initialCount+1 {
+		t.Errorf("Found %d record(s) in test database, expecting %d.", nextCount, initialCount+1)
+	}
+
+	sourceUrl = url.LongUrl{
+		Value: "http://www2.example.com",
+	}
+	_, err = strategy.Alias(sourceUrl, storage.Service)
+	if err != nil {
+		t.Errorf("Failed during Alias(): %+v", err)
+	}
+
+	nextCount = strategy.UseCount(storage.Service)
+	if nextCount != initialCount+2 {
+		t.Errorf("Found %d record(s) in test database, expecting %d.", nextCount, initialCount+2)
+	}
+
+	storage.Service.DB.Exec("TRUNCATE shorturl")
+}

+ 11 - 0
url/longurl.go

@@ -1,8 +1,19 @@
 package url
 
+import "github.com/FGM/kurz/storage"
+
 type LongUrl struct {
+	Value string
 }
 
 func (l LongUrl) Domain() string {
 	return "default"
 }
+
+type LongMeta struct {
+	Url       LongUrl
+	MimeType  string
+	Language  storage.Language
+	ImagePath string
+	Origin    storage.EventInfo
+}

+ 1 - 0
url/shorturl.go

@@ -5,6 +5,7 @@ import (
 )
 
 type ShortUrl struct {
+	Id          int64
 	Value       string
 	ShortFor    LongUrl
 	Domain      string